diff options
Diffstat (limited to 'lib/Tooling')
-rw-r--r-- | lib/Tooling/ArgumentsAdjusters.cpp | 2 | ||||
-rw-r--r-- | lib/Tooling/CMakeLists.txt | 8 | ||||
-rw-r--r-- | lib/Tooling/CompilationDatabase.cpp | 8 | ||||
-rw-r--r-- | lib/Tooling/Core/Lookup.cpp | 94 | ||||
-rw-r--r-- | lib/Tooling/Core/QualTypeNames.cpp | 2 | ||||
-rw-r--r-- | lib/Tooling/Core/Replacement.cpp | 501 | ||||
-rw-r--r-- | lib/Tooling/JSONCompilationDatabase.cpp | 60 | ||||
-rw-r--r-- | lib/Tooling/Refactoring.cpp | 23 | ||||
-rw-r--r-- | lib/Tooling/RefactoringCallbacks.cpp | 50 | ||||
-rw-r--r-- | lib/Tooling/Tooling.cpp | 10 |
10 files changed, 499 insertions, 259 deletions
diff --git a/lib/Tooling/ArgumentsAdjusters.cpp b/lib/Tooling/ArgumentsAdjusters.cpp index 2f3d829d7d197..48b925c698a7c 100644 --- a/lib/Tooling/ArgumentsAdjusters.cpp +++ b/lib/Tooling/ArgumentsAdjusters.cpp @@ -17,7 +17,7 @@ namespace clang { namespace tooling { -/// Add -fsyntax-only option to the commnand line arguments. +/// Add -fsyntax-only option to the command line arguments. ArgumentsAdjuster getClangSyntaxOnlyAdjuster() { return [](const CommandLineArguments &Args, StringRef /*unused*/) { CommandLineArguments AdjustedArgs; diff --git a/lib/Tooling/CMakeLists.txt b/lib/Tooling/CMakeLists.txt index 56134c1164d4e..2eec1dba2f363 100644 --- a/lib/Tooling/CMakeLists.txt +++ b/lib/Tooling/CMakeLists.txt @@ -1,4 +1,7 @@ -set(LLVM_LINK_COMPONENTS support) +set(LLVM_LINK_COMPONENTS + Option + Support + ) add_subdirectory(Core) @@ -13,6 +16,9 @@ add_clang_library(clangTooling RefactoringCallbacks.cpp Tooling.cpp + DEPENDS + ClangDriverOptions + LINK_LIBS clangAST clangASTMatchers diff --git a/lib/Tooling/CompilationDatabase.cpp b/lib/Tooling/CompilationDatabase.cpp index 8fc4a1fe5beb0..8ca0b2df70130 100644 --- a/lib/Tooling/CompilationDatabase.cpp +++ b/lib/Tooling/CompilationDatabase.cpp @@ -32,12 +32,14 @@ using namespace clang; using namespace tooling; +LLVM_INSTANTIATE_REGISTRY(CompilationDatabasePluginRegistry) + CompilationDatabase::~CompilationDatabase() {} std::unique_ptr<CompilationDatabase> CompilationDatabase::loadFromDirectory(StringRef BuildDirectory, std::string &ErrorMessage) { - std::stringstream ErrorStream; + llvm::raw_string_ostream ErrorStream(ErrorMessage); for (CompilationDatabasePluginRegistry::iterator It = CompilationDatabasePluginRegistry::begin(), Ie = CompilationDatabasePluginRegistry::end(); @@ -49,7 +51,6 @@ CompilationDatabase::loadFromDirectory(StringRef BuildDirectory, return DB; ErrorStream << It->getName() << ": " << DatabaseErrorMessage << "\n"; } - ErrorMessage = ErrorStream.str(); return nullptr; } @@ -299,7 +300,8 @@ FixedCompilationDatabase(Twine Directory, ArrayRef<std::string> CommandLine) { ToolCommandLine.insert(ToolCommandLine.end(), CommandLine.begin(), CommandLine.end()); CompileCommands.emplace_back(Directory, StringRef(), - std::move(ToolCommandLine)); + std::move(ToolCommandLine), + StringRef()); } std::vector<CompileCommand> diff --git a/lib/Tooling/Core/Lookup.cpp b/lib/Tooling/Core/Lookup.cpp index 697eeb46ce41e..6edf61b8050db 100644 --- a/lib/Tooling/Core/Lookup.cpp +++ b/lib/Tooling/Core/Lookup.cpp @@ -13,37 +13,69 @@ #include "clang/Tooling/Core/Lookup.h" #include "clang/AST/Decl.h" +#include "clang/AST/DeclCXX.h" using namespace clang; using namespace clang::tooling; -static bool isInsideDifferentNamespaceWithSameName(const DeclContext *DeclA, - const DeclContext *DeclB) { - while (true) { - // Look past non-namespaces on DeclA. - while (DeclA && !isa<NamespaceDecl>(DeclA)) - DeclA = DeclA->getParent(); - - // Look past non-namespaces on DeclB. - while (DeclB && !isa<NamespaceDecl>(DeclB)) - DeclB = DeclB->getParent(); - - // We hit the root, no namespace collision. - if (!DeclA || !DeclB) - return false; +// Gets all namespaces that \p Context is in as a vector (ignoring anonymous +// namespaces). The inner namespaces come before outer namespaces in the vector. +// For example, if the context is in the following namespace: +// `namespace a { namespace b { namespace c ( ... ) } }`, +// the vector will be `{c, b, a}`. +static llvm::SmallVector<const NamespaceDecl *, 4> +getAllNamedNamespaces(const DeclContext *Context) { + llvm::SmallVector<const NamespaceDecl *, 4> Namespaces; + auto GetNextNamedNamespace = [](const DeclContext *Context) { + // Look past non-namespaces and anonymous namespaces on FromContext. + while (Context && (!isa<NamespaceDecl>(Context) || + cast<NamespaceDecl>(Context)->isAnonymousNamespace())) + Context = Context->getParent(); + return Context; + }; + for (Context = GetNextNamedNamespace(Context); Context != nullptr; + Context = GetNextNamedNamespace(Context->getParent())) + Namespaces.push_back(cast<NamespaceDecl>(Context)); + return Namespaces; +} +// Returns true if the context in which the type is used and the context in +// which the type is declared are the same semantical namespace but different +// lexical namespaces. +static bool +usingFromDifferentCanonicalNamespace(const DeclContext *FromContext, + const DeclContext *UseContext) { + // We can skip anonymous namespace because: + // 1. `FromContext` and `UseContext` must be in the same anonymous namespaces + // since referencing across anonymous namespaces is not possible. + // 2. If `FromContext` and `UseContext` are in the same anonymous namespace, + // the function will still return `false` as expected. + llvm::SmallVector<const NamespaceDecl *, 4> FromNamespaces = + getAllNamedNamespaces(FromContext); + llvm::SmallVector<const NamespaceDecl *, 4> UseNamespaces = + getAllNamedNamespaces(UseContext); + // If `UseContext` has fewer level of nested namespaces, it cannot be in the + // same canonical namespace as the `FromContext`. + if (UseNamespaces.size() < FromNamespaces.size()) + return false; + unsigned Diff = UseNamespaces.size() - FromNamespaces.size(); + auto FromIter = FromNamespaces.begin(); + // Only compare `FromNamespaces` with namespaces in `UseNamespaces` that can + // collide, i.e. the top N namespaces where N is the number of namespaces in + // `FromNamespaces`. + auto UseIter = UseNamespaces.begin() + Diff; + for (; FromIter != FromNamespaces.end() && UseIter != UseNamespaces.end(); + ++FromIter, ++UseIter) { // Literally the same namespace, not a collision. - if (DeclA == DeclB) + if (*FromIter == *UseIter) return false; - - // Now check the names. If they match we have a different namespace with the - // same name. - if (cast<NamespaceDecl>(DeclA)->getDeclName() == - cast<NamespaceDecl>(DeclB)->getDeclName()) + // Now check the names. If they match we have a different canonical + // namespace with the same name. + if (cast<NamespaceDecl>(*FromIter)->getDeclName() == + cast<NamespaceDecl>(*UseIter)->getDeclName()) return true; - - DeclA = DeclA->getParent(); - DeclB = DeclB->getParent(); } + assert(FromIter == FromNamespaces.end() && UseIter == UseNamespaces.end()); + return false; } static StringRef getBestNamespaceSubstr(const DeclContext *DeclA, @@ -90,16 +122,22 @@ std::string tooling::replaceNestedName(const NestedNameSpecifier *Use, "Expected fully-qualified name!"); // We can do a raw name replacement when we are not inside the namespace for - // the original function and it is not in the global namespace. The + // the original class/function and it is not in the global namespace. The // assumption is that outside the original namespace we must have a using // statement that makes this work out and that other parts of this refactor - // will automatically fix using statements to point to the new function + // will automatically fix using statements to point to the new class/function. + // However, if the `FromDecl` is a class forward declaration, the reference is + // still considered as referring to the original definition, so we can't do a + // raw name replacement in this case. const bool class_name_only = !Use; const bool in_global_namespace = isa<TranslationUnitDecl>(FromDecl->getDeclContext()); - if (class_name_only && !in_global_namespace && - !isInsideDifferentNamespaceWithSameName(FromDecl->getDeclContext(), - UseContext)) { + const bool is_class_forward_decl = + isa<CXXRecordDecl>(FromDecl) && + !cast<CXXRecordDecl>(FromDecl)->isCompleteDefinition(); + if (class_name_only && !in_global_namespace && !is_class_forward_decl && + !usingFromDifferentCanonicalNamespace(FromDecl->getDeclContext(), + UseContext)) { auto Pos = ReplacementString.rfind("::"); return Pos != StringRef::npos ? ReplacementString.substr(Pos + 2) : ReplacementString; diff --git a/lib/Tooling/Core/QualTypeNames.cpp b/lib/Tooling/Core/QualTypeNames.cpp index 619dae1ee106f..721c2c92fc274 100644 --- a/lib/Tooling/Core/QualTypeNames.cpp +++ b/lib/Tooling/Core/QualTypeNames.cpp @@ -14,8 +14,6 @@ #include "clang/AST/DeclarationName.h" #include "clang/AST/GlobalDecl.h" #include "clang/AST/Mangle.h" -#include "llvm/ADT/ArrayRef.h" -#include "llvm/ADT/StringRef.h" #include <stdio.h> #include <memory> diff --git a/lib/Tooling/Core/Replacement.cpp b/lib/Tooling/Core/Replacement.cpp index 4f130709ac16d..e194b59a6e2b1 100644 --- a/lib/Tooling/Core/Replacement.cpp +++ b/lib/Tooling/Core/Replacement.cpp @@ -20,6 +20,7 @@ #include "clang/Basic/SourceManager.h" #include "clang/Lex/Lexer.h" #include "clang/Rewrite/Core/Rewriter.h" +#include "llvm/ADT/SmallPtrSet.h" #include "llvm/Support/FileSystem.h" #include "llvm/Support/Path.h" #include "llvm/Support/raw_os_ostream.h" @@ -29,8 +30,7 @@ namespace tooling { static const char * const InvalidLocation = ""; -Replacement::Replacement() - : FilePath(InvalidLocation) {} +Replacement::Replacement() : FilePath(InvalidLocation) {} Replacement::Replacement(StringRef FilePath, unsigned Offset, unsigned Length, StringRef ReplacementText) @@ -84,11 +84,8 @@ bool operator<(const Replacement &LHS, const Replacement &RHS) { if (LHS.getOffset() != RHS.getOffset()) return LHS.getOffset() < RHS.getOffset(); - // Apply longer replacements first, specifically so that deletions are - // executed before insertions. It is (hopefully) never the intention to - // delete parts of newly inserted code. if (LHS.getLength() != RHS.getLength()) - return LHS.getLength() > RHS.getLength(); + return LHS.getLength() < RHS.getLength(); if (LHS.getFilePath() != RHS.getFilePath()) return LHS.getFilePath() < RHS.getFilePath(); @@ -138,200 +135,196 @@ void Replacement::setFromSourceRange(const SourceManager &Sources, ReplacementText); } -template <typename T> -unsigned shiftedCodePositionInternal(const T &Replaces, unsigned Position) { - unsigned Offset = 0; - for (const auto& R : Replaces) { - if (R.getOffset() + R.getLength() <= Position) { - Offset += R.getReplacementText().size() - R.getLength(); - continue; - } - if (R.getOffset() < Position && - R.getOffset() + R.getReplacementText().size() <= Position) { - Position = R.getOffset() + R.getReplacementText().size() - 1; - } - break; - } - return Position + Offset; +Replacement +Replacements::getReplacementInChangedCode(const Replacement &R) const { + unsigned NewStart = getShiftedCodePosition(R.getOffset()); + unsigned NewEnd = getShiftedCodePosition(R.getOffset() + R.getLength()); + return Replacement(R.getFilePath(), NewStart, NewEnd - NewStart, + R.getReplacementText()); } -unsigned shiftedCodePosition(const Replacements &Replaces, unsigned Position) { - return shiftedCodePositionInternal(Replaces, Position); +static std::string getReplacementErrString(replacement_error Err) { + switch (Err) { + case replacement_error::fail_to_apply: + return "Failed to apply a replacement."; + case replacement_error::wrong_file_path: + return "The new replacement's file path is different from the file path of " + "existing replacements"; + case replacement_error::overlap_conflict: + return "The new replacement overlaps with an existing replacement."; + case replacement_error::insert_conflict: + return "The new insertion has the same insert location as an existing " + "replacement."; + } + llvm_unreachable("A value of replacement_error has no message."); } -// FIXME: Remove this function when Replacements is implemented as std::vector -// instead of std::set. -unsigned shiftedCodePosition(const std::vector<Replacement> &Replaces, - unsigned Position) { - return shiftedCodePositionInternal(Replaces, Position); +std::string ReplacementError::message() const { + std::string Message = getReplacementErrString(Err); + if (NewReplacement.hasValue()) + Message += "\nNew replacement: " + NewReplacement->toString(); + if (ExistingReplacement.hasValue()) + Message += "\nExisting replacement: " + ExistingReplacement->toString(); + return Message; } -void deduplicate(std::vector<Replacement> &Replaces, - std::vector<Range> &Conflicts) { - if (Replaces.empty()) - return; - - auto LessNoPath = [](const Replacement &LHS, const Replacement &RHS) { - if (LHS.getOffset() != RHS.getOffset()) - return LHS.getOffset() < RHS.getOffset(); - if (LHS.getLength() != RHS.getLength()) - return LHS.getLength() < RHS.getLength(); - return LHS.getReplacementText() < RHS.getReplacementText(); - }; - - auto EqualNoPath = [](const Replacement &LHS, const Replacement &RHS) { - return LHS.getOffset() == RHS.getOffset() && - LHS.getLength() == RHS.getLength() && - LHS.getReplacementText() == RHS.getReplacementText(); - }; +char ReplacementError::ID = 0; - // Deduplicate. We don't want to deduplicate based on the path as we assume - // that all replacements refer to the same file (or are symlinks). - std::sort(Replaces.begin(), Replaces.end(), LessNoPath); - Replaces.erase(std::unique(Replaces.begin(), Replaces.end(), EqualNoPath), - Replaces.end()); - - // Detect conflicts - Range ConflictRange(Replaces.front().getOffset(), - Replaces.front().getLength()); - unsigned ConflictStart = 0; - unsigned ConflictLength = 1; - for (unsigned i = 1; i < Replaces.size(); ++i) { - Range Current(Replaces[i].getOffset(), Replaces[i].getLength()); - if (ConflictRange.overlapsWith(Current)) { - // Extend conflicted range - ConflictRange = Range(ConflictRange.getOffset(), - std::max(ConflictRange.getLength(), - Current.getOffset() + Current.getLength() - - ConflictRange.getOffset())); - ++ConflictLength; - } else { - if (ConflictLength > 1) - Conflicts.push_back(Range(ConflictStart, ConflictLength)); - ConflictRange = Current; - ConflictStart = i; - ConflictLength = 1; +Replacements Replacements::getCanonicalReplacements() const { + std::vector<Replacement> NewReplaces; + // Merge adjacent replacements. + for (const auto &R : Replaces) { + if (NewReplaces.empty()) { + NewReplaces.push_back(R); + continue; } - } - - if (ConflictLength > 1) - Conflicts.push_back(Range(ConflictStart, ConflictLength)); -} - -bool applyAllReplacements(const Replacements &Replaces, Rewriter &Rewrite) { - bool Result = true; - for (Replacements::const_iterator I = Replaces.begin(), - E = Replaces.end(); - I != E; ++I) { - if (I->isApplicable()) { - Result = I->apply(Rewrite) && Result; + auto &Prev = NewReplaces.back(); + unsigned PrevEnd = Prev.getOffset() + Prev.getLength(); + if (PrevEnd < R.getOffset()) { + NewReplaces.push_back(R); } else { - Result = false; + assert(PrevEnd == R.getOffset() && + "Existing replacements must not overlap."); + Replacement NewR( + R.getFilePath(), Prev.getOffset(), Prev.getLength() + R.getLength(), + (Prev.getReplacementText() + R.getReplacementText()).str()); + Prev = NewR; } } - return Result; + ReplacementsImpl NewReplacesImpl(NewReplaces.begin(), NewReplaces.end()); + return Replacements(NewReplacesImpl.begin(), NewReplacesImpl.end()); } -// FIXME: Remove this function when Replacements is implemented as std::vector -// instead of std::set. -bool applyAllReplacements(const std::vector<Replacement> &Replaces, - Rewriter &Rewrite) { - bool Result = true; - for (std::vector<Replacement>::const_iterator I = Replaces.begin(), - E = Replaces.end(); - I != E; ++I) { - if (I->isApplicable()) { - Result = I->apply(Rewrite) && Result; - } else { - Result = false; - } - } - return Result; +// `R` and `Replaces` are order-independent if applying them in either order +// has the same effect, so we need to compare replacements associated to +// applying them in either order. +llvm::Expected<Replacements> +Replacements::mergeIfOrderIndependent(const Replacement &R) const { + Replacements Rs(R); + // A Replacements set containg a single replacement that is `R` referring to + // the code after the existing replacements `Replaces` are applied. + Replacements RsShiftedByReplaces(getReplacementInChangedCode(R)); + // A Replacements set that is `Replaces` referring to the code after `R` is + // applied. + Replacements ReplacesShiftedByRs; + for (const auto &Replace : Replaces) + ReplacesShiftedByRs.Replaces.insert( + Rs.getReplacementInChangedCode(Replace)); + // This is equivalent to applying `Replaces` first and then `R`. + auto MergeShiftedRs = merge(RsShiftedByReplaces); + // This is equivalent to applying `R` first and then `Replaces`. + auto MergeShiftedReplaces = Rs.merge(ReplacesShiftedByRs); + + // Since empty or segmented replacements around existing replacements might be + // produced above, we need to compare replacements in canonical forms. + if (MergeShiftedRs.getCanonicalReplacements() == + MergeShiftedReplaces.getCanonicalReplacements()) + return MergeShiftedRs; + return llvm::make_error<ReplacementError>(replacement_error::overlap_conflict, + R, *Replaces.begin()); } -llvm::Expected<std::string> applyAllReplacements(StringRef Code, - const Replacements &Replaces) { - if (Replaces.empty()) - return Code.str(); +llvm::Error Replacements::add(const Replacement &R) { + // Check the file path. + if (!Replaces.empty() && R.getFilePath() != Replaces.begin()->getFilePath()) + return llvm::make_error<ReplacementError>( + replacement_error::wrong_file_path, R, *Replaces.begin()); - IntrusiveRefCntPtr<vfs::InMemoryFileSystem> InMemoryFileSystem( - new vfs::InMemoryFileSystem); - FileManager Files(FileSystemOptions(), InMemoryFileSystem); - DiagnosticsEngine Diagnostics( - IntrusiveRefCntPtr<DiagnosticIDs>(new DiagnosticIDs), - new DiagnosticOptions); - SourceManager SourceMgr(Diagnostics, Files); - Rewriter Rewrite(SourceMgr, LangOptions()); - InMemoryFileSystem->addFile( - "<stdin>", 0, llvm::MemoryBuffer::getMemBuffer(Code, "<stdin>")); - FileID ID = SourceMgr.createFileID(Files.getFile("<stdin>"), SourceLocation(), - clang::SrcMgr::C_User); - for (Replacements::const_iterator I = Replaces.begin(), E = Replaces.end(); - I != E; ++I) { - Replacement Replace("<stdin>", I->getOffset(), I->getLength(), - I->getReplacementText()); - if (!Replace.apply(Rewrite)) - return llvm::make_error<llvm::StringError>( - "Failed to apply replacement: " + Replace.toString(), - llvm::inconvertibleErrorCode()); + // Special-case header insertions. + if (R.getOffset() == UINT_MAX) { + Replaces.insert(R); + return llvm::Error::success(); } - std::string Result; - llvm::raw_string_ostream OS(Result); - Rewrite.getEditBuffer(ID).write(OS); - OS.flush(); - return Result; -} -// Merge and sort overlapping ranges in \p Ranges. -static std::vector<Range> mergeAndSortRanges(std::vector<Range> Ranges) { - std::sort(Ranges.begin(), Ranges.end(), - [](const Range &LHS, const Range &RHS) { - if (LHS.getOffset() != RHS.getOffset()) - return LHS.getOffset() < RHS.getOffset(); - return LHS.getLength() < RHS.getLength(); - }); - std::vector<Range> Result; - for (const auto &R : Ranges) { - if (Result.empty() || - Result.back().getOffset() + Result.back().getLength() < R.getOffset()) { - Result.push_back(R); - } else { - unsigned NewEnd = - std::max(Result.back().getOffset() + Result.back().getLength(), - R.getOffset() + R.getLength()); - Result[Result.size() - 1] = - Range(Result.back().getOffset(), NewEnd - Result.back().getOffset()); + // This replacement cannot conflict with replacements that end before + // this replacement starts or start after this replacement ends. + // We also know that there currently are no overlapping replacements. + // Thus, we know that all replacements that start after the end of the current + // replacement cannot overlap. + Replacement AtEnd(R.getFilePath(), R.getOffset() + R.getLength(), 0, ""); + + // Find the first entry that starts after or at the end of R. Note that + // entries that start at the end can still be conflicting if R is an + // insertion. + auto I = Replaces.lower_bound(AtEnd); + // If `I` starts at the same offset as `R`, `R` must be an insertion. + if (I != Replaces.end() && R.getOffset() == I->getOffset()) { + assert(R.getLength() == 0); + // `I` is also an insertion, `R` and `I` conflict. + if (I->getLength() == 0) { + // Check if two insertions are order-indepedent: if inserting them in + // either order produces the same text, they are order-independent. + if ((R.getReplacementText() + I->getReplacementText()).str() != + (I->getReplacementText() + R.getReplacementText()).str()) + return llvm::make_error<ReplacementError>( + replacement_error::insert_conflict, R, *I); + // If insertions are order-independent, we can merge them. + Replacement NewR( + R.getFilePath(), R.getOffset(), 0, + (R.getReplacementText() + I->getReplacementText()).str()); + Replaces.erase(I); + Replaces.insert(std::move(NewR)); + return llvm::Error::success(); } + // Insertion `R` is adjacent to a non-insertion replacement `I`, so they + // are order-independent. It is safe to assume that `R` will not conflict + // with any replacement before `I` since all replacements before `I` must + // either end before `R` or end at `R` but has length > 0 (if the + // replacement before `I` is an insertion at `R`, it would have been `I` + // since it is a lower bound of `AtEnd` and ordered before the current `I` + // in the set). + Replaces.insert(R); + return llvm::Error::success(); } - return Result; -} -std::vector<Range> calculateChangedRanges(const Replacements &Replaces) { - std::vector<Range> ChangedRanges; - int Shift = 0; - for (const Replacement &R : Replaces) { - unsigned Offset = R.getOffset() + Shift; - unsigned Length = R.getReplacementText().size(); - Shift += Length - R.getLength(); - ChangedRanges.push_back(Range(Offset, Length)); + // `I` is the smallest iterator (after `R`) whose entry cannot overlap. + // If that is begin(), there are no overlaps. + if (I == Replaces.begin()) { + Replaces.insert(R); + return llvm::Error::success(); } - return mergeAndSortRanges(ChangedRanges); -} - -std::vector<Range> -calculateRangesAfterReplacements(const Replacements &Replaces, - const std::vector<Range> &Ranges) { - auto MergedRanges = mergeAndSortRanges(Ranges); - tooling::Replacements FakeReplaces; - for (const auto &R : MergedRanges) - FakeReplaces.insert(Replacement(Replaces.begin()->getFilePath(), - R.getOffset(), R.getLength(), - std::string(R.getLength(), ' '))); - tooling::Replacements NewReplaces = mergeReplacements(FakeReplaces, Replaces); - return calculateChangedRanges(NewReplaces); + --I; + auto Overlap = [](const Replacement &R1, const Replacement &R2) -> bool { + return Range(R1.getOffset(), R1.getLength()) + .overlapsWith(Range(R2.getOffset(), R2.getLength())); + }; + // If the previous entry does not overlap, we know that entries before it + // can also not overlap. + if (!Overlap(R, *I)) { + // If `R` and `I` do not have the same offset, it is safe to add `R` since + // it must come after `I`. Otherwise: + // - If `R` is an insertion, `I` must not be an insertion since it would + // have come after `AtEnd`. + // - If `R` is not an insertion, `I` must be an insertion; otherwise, `R` + // and `I` would have overlapped. + // In either case, we can safely insert `R`. + Replaces.insert(R); + } else { + // `I` overlaps with `R`. We need to check `R` against all overlapping + // replacements to see if they are order-indepedent. If they are, merge `R` + // with them and replace them with the merged replacements. + auto MergeBegin = I; + auto MergeEnd = std::next(I); + while (I != Replaces.begin()) { + --I; + // If `I` doesn't overlap with `R`, don't merge it. + if (!Overlap(R, *I)) + break; + MergeBegin = I; + } + Replacements OverlapReplaces(MergeBegin, MergeEnd); + llvm::Expected<Replacements> Merged = + OverlapReplaces.mergeIfOrderIndependent(R); + if (!Merged) + return Merged.takeError(); + Replaces.erase(MergeBegin, MergeEnd); + Replaces.insert(Merged->begin(), Merged->end()); + } + return llvm::Error::success(); } namespace { + // Represents a merged replacement, i.e. a replacement consisting of multiple // overlapping replacements from 'First' and 'Second' in mergeReplacements. // @@ -425,26 +418,19 @@ private: unsigned Length; std::string Text; }; -} // namespace -std::map<std::string, Replacements> -groupReplacementsByFile(const Replacements &Replaces) { - std::map<std::string, Replacements> FileToReplaces; - for (const auto &Replace : Replaces) { - FileToReplaces[Replace.getFilePath()].insert(Replace); - } - return FileToReplaces; -} +} // namespace -Replacements mergeReplacements(const Replacements &First, - const Replacements &Second) { - if (First.empty() || Second.empty()) - return First.empty() ? Second : First; +Replacements Replacements::merge(const Replacements &ReplacesToMerge) const { + if (empty() || ReplacesToMerge.empty()) + return empty() ? ReplacesToMerge : *this; + auto &First = Replaces; + auto &Second = ReplacesToMerge.Replaces; // Delta is the amount of characters that replacements from 'Second' need to // be shifted so that their offsets refer to the original text. int Delta = 0; - Replacements Result; + ReplacementsImpl Result; // Iterate over both sets and always add the next element (smallest total // Offset) from either 'First' or 'Second'. Merge that element with @@ -470,6 +456,143 @@ Replacements mergeReplacements(const Replacements &First, Delta -= Merged.deltaFirst(); Result.insert(Merged.asReplacement()); } + return Replacements(Result.begin(), Result.end()); +} + +// Combines overlapping ranges in \p Ranges and sorts the combined ranges. +// Returns a set of non-overlapping and sorted ranges that is equivalent to +// \p Ranges. +static std::vector<Range> combineAndSortRanges(std::vector<Range> Ranges) { + std::sort(Ranges.begin(), Ranges.end(), + [](const Range &LHS, const Range &RHS) { + if (LHS.getOffset() != RHS.getOffset()) + return LHS.getOffset() < RHS.getOffset(); + return LHS.getLength() < RHS.getLength(); + }); + std::vector<Range> Result; + for (const auto &R : Ranges) { + if (Result.empty() || + Result.back().getOffset() + Result.back().getLength() < R.getOffset()) { + Result.push_back(R); + } else { + unsigned NewEnd = + std::max(Result.back().getOffset() + Result.back().getLength(), + R.getOffset() + R.getLength()); + Result[Result.size() - 1] = + Range(Result.back().getOffset(), NewEnd - Result.back().getOffset()); + } + } + return Result; +} + +std::vector<Range> +calculateRangesAfterReplacements(const Replacements &Replaces, + const std::vector<Range> &Ranges) { + // To calculate the new ranges, + // - Turn \p Ranges into Replacements at (offset, length) with an empty + // (unimportant) replacement text of length "length". + // - Merge with \p Replaces. + // - The new ranges will be the affected ranges of the merged replacements. + auto MergedRanges = combineAndSortRanges(Ranges); + if (Replaces.empty()) + return MergedRanges; + tooling::Replacements FakeReplaces; + for (const auto &R : MergedRanges) { + auto Err = FakeReplaces.add(Replacement(Replaces.begin()->getFilePath(), + R.getOffset(), R.getLength(), + std::string(R.getLength(), ' '))); + assert(!Err && + "Replacements must not conflict since ranges have been merged."); + (void)Err; + } + return FakeReplaces.merge(Replaces).getAffectedRanges(); +} + +std::vector<Range> Replacements::getAffectedRanges() const { + std::vector<Range> ChangedRanges; + int Shift = 0; + for (const Replacement &R : Replaces) { + unsigned Offset = R.getOffset() + Shift; + unsigned Length = R.getReplacementText().size(); + Shift += Length - R.getLength(); + ChangedRanges.push_back(Range(Offset, Length)); + } + return combineAndSortRanges(ChangedRanges); +} + +unsigned Replacements::getShiftedCodePosition(unsigned Position) const { + unsigned Offset = 0; + for (const auto& R : Replaces) { + if (R.getOffset() + R.getLength() <= Position) { + Offset += R.getReplacementText().size() - R.getLength(); + continue; + } + if (R.getOffset() < Position && + R.getOffset() + R.getReplacementText().size() <= Position) { + Position = R.getOffset() + R.getReplacementText().size(); + if (R.getReplacementText().size() > 0) + Position--; + } + break; + } + return Position + Offset; +} + +bool applyAllReplacements(const Replacements &Replaces, Rewriter &Rewrite) { + bool Result = true; + for (auto I = Replaces.rbegin(), E = Replaces.rend(); I != E; ++I) { + if (I->isApplicable()) { + Result = I->apply(Rewrite) && Result; + } else { + Result = false; + } + } + return Result; +} + +llvm::Expected<std::string> applyAllReplacements(StringRef Code, + const Replacements &Replaces) { + if (Replaces.empty()) + return Code.str(); + + IntrusiveRefCntPtr<vfs::InMemoryFileSystem> InMemoryFileSystem( + new vfs::InMemoryFileSystem); + FileManager Files(FileSystemOptions(), InMemoryFileSystem); + DiagnosticsEngine Diagnostics( + IntrusiveRefCntPtr<DiagnosticIDs>(new DiagnosticIDs), + new DiagnosticOptions); + SourceManager SourceMgr(Diagnostics, Files); + Rewriter Rewrite(SourceMgr, LangOptions()); + InMemoryFileSystem->addFile( + "<stdin>", 0, llvm::MemoryBuffer::getMemBuffer(Code, "<stdin>")); + FileID ID = SourceMgr.createFileID(Files.getFile("<stdin>"), SourceLocation(), + clang::SrcMgr::C_User); + for (auto I = Replaces.rbegin(), E = Replaces.rend(); I != E; ++I) { + Replacement Replace("<stdin>", I->getOffset(), I->getLength(), + I->getReplacementText()); + if (!Replace.apply(Rewrite)) + return llvm::make_error<ReplacementError>( + replacement_error::fail_to_apply, Replace); + } + std::string Result; + llvm::raw_string_ostream OS(Result); + Rewrite.getEditBuffer(ID).write(OS); + OS.flush(); + return Result; +} + +std::map<std::string, Replacements> groupReplacementsByFile( + FileManager &FileMgr, + const std::map<std::string, Replacements> &FileToReplaces) { + std::map<std::string, Replacements> Result; + llvm::SmallPtrSet<const FileEntry *, 16> ProcessedFileEntries; + for (const auto &Entry : FileToReplaces) { + const FileEntry *FE = FileMgr.getFile(Entry.first); + if (!FE) + llvm::errs() << "File path " << Entry.first << " is invalid.\n"; + else if (ProcessedFileEntries.insert(FE).second) + Result[Entry.first] = std::move(Entry.second); + } return Result; } diff --git a/lib/Tooling/JSONCompilationDatabase.cpp b/lib/Tooling/JSONCompilationDatabase.cpp index 299fbdc149bfb..738e610ed9469 100644 --- a/lib/Tooling/JSONCompilationDatabase.cpp +++ b/lib/Tooling/JSONCompilationDatabase.cpp @@ -16,7 +16,10 @@ #include "clang/Tooling/CompilationDatabasePluginRegistry.h" #include "clang/Tooling/Tooling.h" #include "llvm/ADT/SmallString.h" +#include "llvm/Support/Allocator.h" +#include "llvm/Support/CommandLine.h" #include "llvm/Support/Path.h" +#include "llvm/Support/StringSaver.h" #include <system_error> namespace clang { @@ -111,8 +114,29 @@ class CommandLineArgumentParser { std::vector<std::string> CommandLine; }; -std::vector<std::string> unescapeCommandLine( - StringRef EscapedCommandLine) { +std::vector<std::string> unescapeCommandLine(JSONCommandLineSyntax Syntax, + StringRef EscapedCommandLine) { + if (Syntax == JSONCommandLineSyntax::AutoDetect) { + Syntax = JSONCommandLineSyntax::Gnu; + llvm::Triple Triple(llvm::sys::getProcessTriple()); + if (Triple.getOS() == llvm::Triple::OSType::Win32) { + // Assume Windows command line parsing on Win32 unless the triple + // explicitly tells us otherwise. + if (!Triple.hasEnvironment() || + Triple.getEnvironment() == llvm::Triple::EnvironmentType::MSVC) + Syntax = JSONCommandLineSyntax::Windows; + } + } + + if (Syntax == JSONCommandLineSyntax::Windows) { + llvm::BumpPtrAllocator Alloc; + llvm::StringSaver Saver(Alloc); + llvm::SmallVector<const char *, 64> T; + llvm::cl::TokenizeWindowsCommandLine(EscapedCommandLine, Saver, T); + std::vector<std::string> Result(T.begin(), T.end()); + return Result; + } + assert(Syntax == JSONCommandLineSyntax::Gnu); CommandLineArgumentParser parser(EscapedCommandLine); return parser.parse(); } @@ -123,7 +147,8 @@ class JSONCompilationDatabasePlugin : public CompilationDatabasePlugin { SmallString<1024> JSONDatabasePath(Directory); llvm::sys::path::append(JSONDatabasePath, "compile_commands.json"); std::unique_ptr<CompilationDatabase> Database( - JSONCompilationDatabase::loadFromFile(JSONDatabasePath, ErrorMessage)); + JSONCompilationDatabase::loadFromFile( + JSONDatabasePath, ErrorMessage, JSONCommandLineSyntax::AutoDetect)); if (!Database) return nullptr; return Database; @@ -143,7 +168,8 @@ volatile int JSONAnchorSource = 0; std::unique_ptr<JSONCompilationDatabase> JSONCompilationDatabase::loadFromFile(StringRef FilePath, - std::string &ErrorMessage) { + std::string &ErrorMessage, + JSONCommandLineSyntax Syntax) { llvm::ErrorOr<std::unique_ptr<llvm::MemoryBuffer>> DatabaseBuffer = llvm::MemoryBuffer::getFile(FilePath); if (std::error_code Result = DatabaseBuffer.getError()) { @@ -151,7 +177,7 @@ JSONCompilationDatabase::loadFromFile(StringRef FilePath, return nullptr; } std::unique_ptr<JSONCompilationDatabase> Database( - new JSONCompilationDatabase(std::move(*DatabaseBuffer))); + new JSONCompilationDatabase(std::move(*DatabaseBuffer), Syntax)); if (!Database->parse(ErrorMessage)) return nullptr; return Database; @@ -159,11 +185,12 @@ JSONCompilationDatabase::loadFromFile(StringRef FilePath, std::unique_ptr<JSONCompilationDatabase> JSONCompilationDatabase::loadFromBuffer(StringRef DatabaseString, - std::string &ErrorMessage) { + std::string &ErrorMessage, + JSONCommandLineSyntax Syntax) { std::unique_ptr<llvm::MemoryBuffer> DatabaseBuffer( llvm::MemoryBuffer::getMemBuffer(DatabaseString)); std::unique_ptr<JSONCompilationDatabase> Database( - new JSONCompilationDatabase(std::move(DatabaseBuffer))); + new JSONCompilationDatabase(std::move(DatabaseBuffer), Syntax)); if (!Database->parse(ErrorMessage)) return nullptr; return Database; @@ -211,10 +238,11 @@ JSONCompilationDatabase::getAllCompileCommands() const { } static std::vector<std::string> -nodeToCommandLine(const std::vector<llvm::yaml::ScalarNode *> &Nodes) { +nodeToCommandLine(JSONCommandLineSyntax Syntax, + const std::vector<llvm::yaml::ScalarNode *> &Nodes) { SmallString<1024> Storage; if (Nodes.size() == 1) { - return unescapeCommandLine(Nodes[0]->getValue(Storage)); + return unescapeCommandLine(Syntax, Nodes[0]->getValue(Storage)); } std::vector<std::string> Arguments; for (auto *Node : Nodes) { @@ -229,10 +257,13 @@ void JSONCompilationDatabase::getCommands( for (int I = 0, E = CommandsRef.size(); I != E; ++I) { SmallString<8> DirectoryStorage; SmallString<32> FilenameStorage; + SmallString<32> OutputStorage; + auto Output = std::get<3>(CommandsRef[I]); Commands.emplace_back( - std::get<0>(CommandsRef[I])->getValue(DirectoryStorage), - std::get<1>(CommandsRef[I])->getValue(FilenameStorage), - nodeToCommandLine(std::get<2>(CommandsRef[I]))); + std::get<0>(CommandsRef[I])->getValue(DirectoryStorage), + std::get<1>(CommandsRef[I])->getValue(FilenameStorage), + nodeToCommandLine(Syntax, std::get<2>(CommandsRef[I])), + Output ? Output->getValue(OutputStorage) : ""); } } @@ -261,6 +292,7 @@ bool JSONCompilationDatabase::parse(std::string &ErrorMessage) { llvm::yaml::ScalarNode *Directory = nullptr; llvm::Optional<std::vector<llvm::yaml::ScalarNode *>> Command; llvm::yaml::ScalarNode *File = nullptr; + llvm::yaml::ScalarNode *Output = nullptr; for (auto& NextKeyValue : *Object) { llvm::yaml::ScalarNode *KeyString = dyn_cast<llvm::yaml::ScalarNode>(NextKeyValue.getKey()); @@ -303,6 +335,8 @@ bool JSONCompilationDatabase::parse(std::string &ErrorMessage) { Command = std::vector<llvm::yaml::ScalarNode *>(1, ValueString); } else if (KeyValue == "file") { File = ValueString; + } else if (KeyValue == "output") { + Output = ValueString; } else { ErrorMessage = ("Unknown key: \"" + KeyString->getRawValue() + "\"").str(); @@ -333,7 +367,7 @@ bool JSONCompilationDatabase::parse(std::string &ErrorMessage) { } else { llvm::sys::path::native(FileName, NativeFilePath); } - auto Cmd = CompileCommandRef(Directory, File, *Command); + auto Cmd = CompileCommandRef(Directory, File, *Command, Output); IndexByFile[NativeFilePath].push_back(Cmd); AllCommands.push_back(Cmd); MatchTrie.insert(NativeFilePath); diff --git a/lib/Tooling/Refactoring.cpp b/lib/Tooling/Refactoring.cpp index 28d535aeb45ff..308c1ac48b281 100644 --- a/lib/Tooling/Refactoring.cpp +++ b/lib/Tooling/Refactoring.cpp @@ -11,6 +11,7 @@ // //===----------------------------------------------------------------------===// +#include "clang/Tooling/Refactoring.h" #include "clang/Basic/DiagnosticOptions.h" #include "clang/Basic/FileManager.h" #include "clang/Basic/SourceManager.h" @@ -18,8 +19,6 @@ #include "clang/Frontend/TextDiagnosticPrinter.h" #include "clang/Lex/Lexer.h" #include "clang/Rewrite/Core/Rewriter.h" -#include "clang/Tooling/Refactoring.h" -#include "llvm/Support/FileSystem.h" #include "llvm/Support/Path.h" #include "llvm/Support/raw_os_ostream.h" @@ -31,7 +30,9 @@ RefactoringTool::RefactoringTool( std::shared_ptr<PCHContainerOperations> PCHContainerOps) : ClangTool(Compilations, SourcePaths, PCHContainerOps) {} -Replacements &RefactoringTool::getReplacements() { return Replace; } +std::map<std::string, Replacements> &RefactoringTool::getReplacements() { + return FileToReplaces; +} int RefactoringTool::runAndSave(FrontendActionFactory *ActionFactory) { if (int Result = run(ActionFactory)) { @@ -55,22 +56,26 @@ int RefactoringTool::runAndSave(FrontendActionFactory *ActionFactory) { } bool RefactoringTool::applyAllReplacements(Rewriter &Rewrite) { - return tooling::applyAllReplacements(Replace, Rewrite); + bool Result = true; + for (const auto &Entry : groupReplacementsByFile( + Rewrite.getSourceMgr().getFileManager(), FileToReplaces)) + Result = tooling::applyAllReplacements(Entry.second, Rewrite) && Result; + return Result; } int RefactoringTool::saveRewrittenFiles(Rewriter &Rewrite) { return Rewrite.overwriteChangedFiles() ? 1 : 0; } -bool formatAndApplyAllReplacements(const Replacements &Replaces, - Rewriter &Rewrite, StringRef Style) { +bool formatAndApplyAllReplacements( + const std::map<std::string, Replacements> &FileToReplaces, Rewriter &Rewrite, + StringRef Style) { SourceManager &SM = Rewrite.getSourceMgr(); FileManager &Files = SM.getFileManager(); - auto FileToReplaces = groupReplacementsByFile(Replaces); - bool Result = true; - for (const auto &FileAndReplaces : FileToReplaces) { + for (const auto &FileAndReplaces : groupReplacementsByFile( + Rewrite.getSourceMgr().getFileManager(), FileToReplaces)) { const std::string &FilePath = FileAndReplaces.first; auto &CurReplaces = FileAndReplaces.second; diff --git a/lib/Tooling/RefactoringCallbacks.cpp b/lib/Tooling/RefactoringCallbacks.cpp index 4de125ec02aa1..e900c23e4f64e 100644 --- a/lib/Tooling/RefactoringCallbacks.cpp +++ b/lib/Tooling/RefactoringCallbacks.cpp @@ -39,11 +39,16 @@ ReplaceStmtWithText::ReplaceStmtWithText(StringRef FromId, StringRef ToText) void ReplaceStmtWithText::run( const ast_matchers::MatchFinder::MatchResult &Result) { - if (const Stmt *FromMatch = Result.Nodes.getStmtAs<Stmt>(FromId)) { - Replace.insert(tooling::Replacement( + if (const Stmt *FromMatch = Result.Nodes.getNodeAs<Stmt>(FromId)) { + auto Err = Replace.add(tooling::Replacement( *Result.SourceManager, - CharSourceRange::getTokenRange(FromMatch->getSourceRange()), - ToText)); + CharSourceRange::getTokenRange(FromMatch->getSourceRange()), ToText)); + // FIXME: better error handling. For now, just print error message in the + // release version. + if (Err) { + llvm::errs() << llvm::toString(std::move(Err)) << "\n"; + assert(false); + } } } @@ -52,11 +57,18 @@ ReplaceStmtWithStmt::ReplaceStmtWithStmt(StringRef FromId, StringRef ToId) void ReplaceStmtWithStmt::run( const ast_matchers::MatchFinder::MatchResult &Result) { - const Stmt *FromMatch = Result.Nodes.getStmtAs<Stmt>(FromId); - const Stmt *ToMatch = Result.Nodes.getStmtAs<Stmt>(ToId); - if (FromMatch && ToMatch) - Replace.insert(replaceStmtWithStmt( - *Result.SourceManager, *FromMatch, *ToMatch)); + const Stmt *FromMatch = Result.Nodes.getNodeAs<Stmt>(FromId); + const Stmt *ToMatch = Result.Nodes.getNodeAs<Stmt>(ToId); + if (FromMatch && ToMatch) { + auto Err = Replace.add( + replaceStmtWithStmt(*Result.SourceManager, *FromMatch, *ToMatch)); + // FIXME: better error handling. For now, just print error message in the + // release version. + if (Err) { + llvm::errs() << llvm::toString(std::move(Err)) << "\n"; + assert(false); + } + } } ReplaceIfStmtWithItsBody::ReplaceIfStmtWithItsBody(StringRef Id, @@ -65,14 +77,28 @@ ReplaceIfStmtWithItsBody::ReplaceIfStmtWithItsBody(StringRef Id, void ReplaceIfStmtWithItsBody::run( const ast_matchers::MatchFinder::MatchResult &Result) { - if (const IfStmt *Node = Result.Nodes.getStmtAs<IfStmt>(Id)) { + if (const IfStmt *Node = Result.Nodes.getNodeAs<IfStmt>(Id)) { const Stmt *Body = PickTrueBranch ? Node->getThen() : Node->getElse(); if (Body) { - Replace.insert(replaceStmtWithStmt(*Result.SourceManager, *Node, *Body)); + auto Err = + Replace.add(replaceStmtWithStmt(*Result.SourceManager, *Node, *Body)); + // FIXME: better error handling. For now, just print error message in the + // release version. + if (Err) { + llvm::errs() << llvm::toString(std::move(Err)) << "\n"; + assert(false); + } } else if (!PickTrueBranch) { // If we want to use the 'else'-branch, but it doesn't exist, delete // the whole 'if'. - Replace.insert(replaceStmtWithText(*Result.SourceManager, *Node, "")); + auto Err = + Replace.add(replaceStmtWithText(*Result.SourceManager, *Node, "")); + // FIXME: better error handling. For now, just print error message in the + // release version. + if (Err) { + llvm::errs() << llvm::toString(std::move(Err)) << "\n"; + assert(false); + } } } } diff --git a/lib/Tooling/Tooling.cpp b/lib/Tooling/Tooling.cpp index 4c7fed1e617c0..529c47ef1e7a8 100644 --- a/lib/Tooling/Tooling.cpp +++ b/lib/Tooling/Tooling.cpp @@ -13,23 +13,26 @@ //===----------------------------------------------------------------------===// #include "clang/Tooling/Tooling.h" -#include "clang/AST/ASTConsumer.h" #include "clang/Driver/Compilation.h" #include "clang/Driver/Driver.h" +#include "clang/Driver/Options.h" #include "clang/Driver/Tool.h" #include "clang/Driver/ToolChain.h" #include "clang/Frontend/ASTUnit.h" #include "clang/Frontend/CompilerInstance.h" #include "clang/Frontend/FrontendDiagnostic.h" #include "clang/Frontend/TextDiagnosticPrinter.h" +#include "clang/Lex/PreprocessorOptions.h" #include "clang/Tooling/ArgumentsAdjusters.h" #include "clang/Tooling/CompilationDatabase.h" #include "llvm/ADT/STLExtras.h" #include "llvm/Config/llvm-config.h" +#include "llvm/Option/ArgList.h" #include "llvm/Option/Option.h" #include "llvm/Support/Debug.h" #include "llvm/Support/FileSystem.h" #include "llvm/Support/Host.h" +#include "llvm/Support/Path.h" #include "llvm/Support/raw_ostream.h" #include <utility> @@ -240,6 +243,11 @@ bool ToolInvocation::run() { Argv.push_back(Str.c_str()); const char *const BinaryName = Argv[0]; IntrusiveRefCntPtr<DiagnosticOptions> DiagOpts = new DiagnosticOptions(); + unsigned MissingArgIndex, MissingArgCount; + std::unique_ptr<llvm::opt::OptTable> Opts(driver::createDriverOptTable()); + llvm::opt::InputArgList ParsedArgs = Opts->ParseArgs( + ArrayRef<const char *>(Argv).slice(1), MissingArgIndex, MissingArgCount); + ParseDiagnosticArgs(*DiagOpts, ParsedArgs); TextDiagnosticPrinter DiagnosticPrinter( llvm::errs(), &*DiagOpts); DiagnosticsEngine Diagnostics( |