diff options
Diffstat (limited to 'contrib/llvm/tools/clang/lib/Tooling')
31 files changed, 8148 insertions, 0 deletions
diff --git a/contrib/llvm/tools/clang/lib/Tooling/ASTDiff/ASTDiff.cpp b/contrib/llvm/tools/clang/lib/Tooling/ASTDiff/ASTDiff.cpp new file mode 100644 index 000000000000..592e8572c770 --- /dev/null +++ b/contrib/llvm/tools/clang/lib/Tooling/ASTDiff/ASTDiff.cpp @@ -0,0 +1,1020 @@ +//===- ASTDiff.cpp - AST differencing implementation-----------*- C++ -*- -===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// This file contains definitons for the AST differencing interface. +// +//===----------------------------------------------------------------------===// + +#include "clang/Tooling/ASTDiff/ASTDiff.h" + +#include "clang/AST/RecursiveASTVisitor.h" +#include "clang/Lex/Lexer.h" +#include "llvm/ADT/PriorityQueue.h" + +#include <limits> +#include <memory> +#include <unordered_set> + +using namespace llvm; +using namespace clang; + +namespace clang { +namespace diff { + +namespace { +/// Maps nodes of the left tree to ones on the right, and vice versa. +class Mapping { +public: + Mapping() = default; + Mapping(Mapping &&Other) = default; + Mapping &operator=(Mapping &&Other) = default; + + Mapping(size_t Size) { + SrcToDst = llvm::make_unique<NodeId[]>(Size); + DstToSrc = llvm::make_unique<NodeId[]>(Size); + } + + void link(NodeId Src, NodeId Dst) { + SrcToDst[Src] = Dst, DstToSrc[Dst] = Src; + } + + NodeId getDst(NodeId Src) const { return SrcToDst[Src]; } + NodeId getSrc(NodeId Dst) const { return DstToSrc[Dst]; } + bool hasSrc(NodeId Src) const { return getDst(Src).isValid(); } + bool hasDst(NodeId Dst) const { return getSrc(Dst).isValid(); } + +private: + std::unique_ptr<NodeId[]> SrcToDst, DstToSrc; +}; +} // end anonymous namespace + +class ASTDiff::Impl { +public: + SyntaxTree::Impl &T1, &T2; + Mapping TheMapping; + + Impl(SyntaxTree::Impl &T1, SyntaxTree::Impl &T2, + const ComparisonOptions &Options); + + /// Matches nodes one-by-one based on their similarity. + void computeMapping(); + + // Compute Change for each node based on similarity. + void computeChangeKinds(Mapping &M); + + NodeId getMapped(const std::unique_ptr<SyntaxTree::Impl> &Tree, + NodeId Id) const { + if (&*Tree == &T1) + return TheMapping.getDst(Id); + assert(&*Tree == &T2 && "Invalid tree."); + return TheMapping.getSrc(Id); + } + +private: + // Returns true if the two subtrees are identical. + bool identical(NodeId Id1, NodeId Id2) const; + + // Returns false if the nodes must not be mached. + bool isMatchingPossible(NodeId Id1, NodeId Id2) const; + + // Returns true if the nodes' parents are matched. + bool haveSameParents(const Mapping &M, NodeId Id1, NodeId Id2) const; + + // Uses an optimal albeit slow algorithm to compute a mapping between two + // subtrees, but only if both have fewer nodes than MaxSize. + void addOptimalMapping(Mapping &M, NodeId Id1, NodeId Id2) const; + + // Computes the ratio of common descendants between the two nodes. + // Descendants are only considered to be equal when they are mapped in M. + double getJaccardSimilarity(const Mapping &M, NodeId Id1, NodeId Id2) const; + + // Returns the node that has the highest degree of similarity. + NodeId findCandidate(const Mapping &M, NodeId Id1) const; + + // Returns a mapping of identical subtrees. + Mapping matchTopDown() const; + + // Tries to match any yet unmapped nodes, in a bottom-up fashion. + void matchBottomUp(Mapping &M) const; + + const ComparisonOptions &Options; + + friend class ZhangShashaMatcher; +}; + +/// Represents the AST of a TranslationUnit. +class SyntaxTree::Impl { +public: + Impl(SyntaxTree *Parent, ASTContext &AST); + /// Constructs a tree from an AST node. + Impl(SyntaxTree *Parent, Decl *N, ASTContext &AST); + Impl(SyntaxTree *Parent, Stmt *N, ASTContext &AST); + template <class T> + Impl(SyntaxTree *Parent, + typename std::enable_if<std::is_base_of<Stmt, T>::value, T>::type *Node, + ASTContext &AST) + : Impl(Parent, dyn_cast<Stmt>(Node), AST) {} + template <class T> + Impl(SyntaxTree *Parent, + typename std::enable_if<std::is_base_of<Decl, T>::value, T>::type *Node, + ASTContext &AST) + : Impl(Parent, dyn_cast<Decl>(Node), AST) {} + + SyntaxTree *Parent; + ASTContext &AST; + PrintingPolicy TypePP; + /// Nodes in preorder. + std::vector<Node> Nodes; + std::vector<NodeId> Leaves; + // Maps preorder indices to postorder ones. + std::vector<int> PostorderIds; + std::vector<NodeId> NodesBfs; + + int getSize() const { return Nodes.size(); } + NodeId getRootId() const { return 0; } + PreorderIterator begin() const { return getRootId(); } + PreorderIterator end() const { return getSize(); } + + const Node &getNode(NodeId Id) const { return Nodes[Id]; } + Node &getMutableNode(NodeId Id) { return Nodes[Id]; } + bool isValidNodeId(NodeId Id) const { return Id >= 0 && Id < getSize(); } + void addNode(Node &N) { Nodes.push_back(N); } + int getNumberOfDescendants(NodeId Id) const; + bool isInSubtree(NodeId Id, NodeId SubtreeRoot) const; + int findPositionInParent(NodeId Id, bool Shifted = false) const; + + std::string getRelativeName(const NamedDecl *ND, + const DeclContext *Context) const; + std::string getRelativeName(const NamedDecl *ND) const; + + std::string getNodeValue(NodeId Id) const; + std::string getNodeValue(const Node &Node) const; + std::string getDeclValue(const Decl *D) const; + std::string getStmtValue(const Stmt *S) const; + +private: + void initTree(); + void setLeftMostDescendants(); +}; + +static bool isSpecializedNodeExcluded(const Decl *D) { return D->isImplicit(); } +static bool isSpecializedNodeExcluded(const Stmt *S) { return false; } +static bool isSpecializedNodeExcluded(CXXCtorInitializer *I) { + return !I->isWritten(); +} + +template <class T> +static bool isNodeExcluded(const SourceManager &SrcMgr, T *N) { + if (!N) + return true; + SourceLocation SLoc = N->getSourceRange().getBegin(); + if (SLoc.isValid()) { + // Ignore everything from other files. + if (!SrcMgr.isInMainFile(SLoc)) + return true; + // Ignore macros. + if (SLoc != SrcMgr.getSpellingLoc(SLoc)) + return true; + } + return isSpecializedNodeExcluded(N); +} + +namespace { +// Sets Height, Parent and Children for each node. +struct PreorderVisitor : public RecursiveASTVisitor<PreorderVisitor> { + int Id = 0, Depth = 0; + NodeId Parent; + SyntaxTree::Impl &Tree; + + PreorderVisitor(SyntaxTree::Impl &Tree) : Tree(Tree) {} + + template <class T> std::tuple<NodeId, NodeId> PreTraverse(T *ASTNode) { + NodeId MyId = Id; + Tree.Nodes.emplace_back(); + Node &N = Tree.getMutableNode(MyId); + N.Parent = Parent; + N.Depth = Depth; + N.ASTNode = DynTypedNode::create(*ASTNode); + assert(!N.ASTNode.getNodeKind().isNone() && + "Expected nodes to have a valid kind."); + if (Parent.isValid()) { + Node &P = Tree.getMutableNode(Parent); + P.Children.push_back(MyId); + } + Parent = MyId; + ++Id; + ++Depth; + return std::make_tuple(MyId, Tree.getNode(MyId).Parent); + } + void PostTraverse(std::tuple<NodeId, NodeId> State) { + NodeId MyId, PreviousParent; + std::tie(MyId, PreviousParent) = State; + assert(MyId.isValid() && "Expecting to only traverse valid nodes."); + Parent = PreviousParent; + --Depth; + Node &N = Tree.getMutableNode(MyId); + N.RightMostDescendant = Id - 1; + assert(N.RightMostDescendant >= 0 && + N.RightMostDescendant < Tree.getSize() && + "Rightmost descendant must be a valid tree node."); + if (N.isLeaf()) + Tree.Leaves.push_back(MyId); + N.Height = 1; + for (NodeId Child : N.Children) + N.Height = std::max(N.Height, 1 + Tree.getNode(Child).Height); + } + bool TraverseDecl(Decl *D) { + if (isNodeExcluded(Tree.AST.getSourceManager(), D)) + return true; + auto SavedState = PreTraverse(D); + RecursiveASTVisitor<PreorderVisitor>::TraverseDecl(D); + PostTraverse(SavedState); + return true; + } + bool TraverseStmt(Stmt *S) { + if (S) + S = S->IgnoreImplicit(); + if (isNodeExcluded(Tree.AST.getSourceManager(), S)) + return true; + auto SavedState = PreTraverse(S); + RecursiveASTVisitor<PreorderVisitor>::TraverseStmt(S); + PostTraverse(SavedState); + return true; + } + bool TraverseType(QualType T) { return true; } + bool TraverseConstructorInitializer(CXXCtorInitializer *Init) { + if (isNodeExcluded(Tree.AST.getSourceManager(), Init)) + return true; + auto SavedState = PreTraverse(Init); + RecursiveASTVisitor<PreorderVisitor>::TraverseConstructorInitializer(Init); + PostTraverse(SavedState); + return true; + } +}; +} // end anonymous namespace + +SyntaxTree::Impl::Impl(SyntaxTree *Parent, ASTContext &AST) + : Parent(Parent), AST(AST), TypePP(AST.getLangOpts()) { + TypePP.AnonymousTagLocations = false; +} + +SyntaxTree::Impl::Impl(SyntaxTree *Parent, Decl *N, ASTContext &AST) + : Impl(Parent, AST) { + PreorderVisitor PreorderWalker(*this); + PreorderWalker.TraverseDecl(N); + initTree(); +} + +SyntaxTree::Impl::Impl(SyntaxTree *Parent, Stmt *N, ASTContext &AST) + : Impl(Parent, AST) { + PreorderVisitor PreorderWalker(*this); + PreorderWalker.TraverseStmt(N); + initTree(); +} + +static std::vector<NodeId> getSubtreePostorder(const SyntaxTree::Impl &Tree, + NodeId Root) { + std::vector<NodeId> Postorder; + std::function<void(NodeId)> Traverse = [&](NodeId Id) { + const Node &N = Tree.getNode(Id); + for (NodeId Child : N.Children) + Traverse(Child); + Postorder.push_back(Id); + }; + Traverse(Root); + return Postorder; +} + +static std::vector<NodeId> getSubtreeBfs(const SyntaxTree::Impl &Tree, + NodeId Root) { + std::vector<NodeId> Ids; + size_t Expanded = 0; + Ids.push_back(Root); + while (Expanded < Ids.size()) + for (NodeId Child : Tree.getNode(Ids[Expanded++]).Children) + Ids.push_back(Child); + return Ids; +} + +void SyntaxTree::Impl::initTree() { + setLeftMostDescendants(); + int PostorderId = 0; + PostorderIds.resize(getSize()); + std::function<void(NodeId)> PostorderTraverse = [&](NodeId Id) { + for (NodeId Child : getNode(Id).Children) + PostorderTraverse(Child); + PostorderIds[Id] = PostorderId; + ++PostorderId; + }; + PostorderTraverse(getRootId()); + NodesBfs = getSubtreeBfs(*this, getRootId()); +} + +void SyntaxTree::Impl::setLeftMostDescendants() { + for (NodeId Leaf : Leaves) { + getMutableNode(Leaf).LeftMostDescendant = Leaf; + NodeId Parent, Cur = Leaf; + while ((Parent = getNode(Cur).Parent).isValid() && + getNode(Parent).Children[0] == Cur) { + Cur = Parent; + getMutableNode(Cur).LeftMostDescendant = Leaf; + } + } +} + +int SyntaxTree::Impl::getNumberOfDescendants(NodeId Id) const { + return getNode(Id).RightMostDescendant - Id + 1; +} + +bool SyntaxTree::Impl::isInSubtree(NodeId Id, NodeId SubtreeRoot) const { + return Id >= SubtreeRoot && Id <= getNode(SubtreeRoot).RightMostDescendant; +} + +int SyntaxTree::Impl::findPositionInParent(NodeId Id, bool Shifted) const { + NodeId Parent = getNode(Id).Parent; + if (Parent.isInvalid()) + return 0; + const auto &Siblings = getNode(Parent).Children; + int Position = 0; + for (size_t I = 0, E = Siblings.size(); I < E; ++I) { + if (Shifted) + Position += getNode(Siblings[I]).Shift; + if (Siblings[I] == Id) { + Position += I; + return Position; + } + } + llvm_unreachable("Node not found in parent's children."); +} + +// Returns the qualified name of ND. If it is subordinate to Context, +// then the prefix of the latter is removed from the returned value. +std::string +SyntaxTree::Impl::getRelativeName(const NamedDecl *ND, + const DeclContext *Context) const { + std::string Val = ND->getQualifiedNameAsString(); + std::string ContextPrefix; + if (!Context) + return Val; + if (auto *Namespace = dyn_cast<NamespaceDecl>(Context)) + ContextPrefix = Namespace->getQualifiedNameAsString(); + else if (auto *Record = dyn_cast<RecordDecl>(Context)) + ContextPrefix = Record->getQualifiedNameAsString(); + else if (AST.getLangOpts().CPlusPlus11) + if (auto *Tag = dyn_cast<TagDecl>(Context)) + ContextPrefix = Tag->getQualifiedNameAsString(); + // Strip the qualifier, if Val refers to something in the current scope. + // But leave one leading ':' in place, so that we know that this is a + // relative path. + if (!ContextPrefix.empty() && StringRef(Val).startswith(ContextPrefix)) + Val = Val.substr(ContextPrefix.size() + 1); + return Val; +} + +std::string SyntaxTree::Impl::getRelativeName(const NamedDecl *ND) const { + return getRelativeName(ND, ND->getDeclContext()); +} + +static const DeclContext *getEnclosingDeclContext(ASTContext &AST, + const Stmt *S) { + while (S) { + const auto &Parents = AST.getParents(*S); + if (Parents.empty()) + return nullptr; + const auto &P = Parents[0]; + if (const auto *D = P.get<Decl>()) + return D->getDeclContext(); + S = P.get<Stmt>(); + } + return nullptr; +} + +static std::string getInitializerValue(const CXXCtorInitializer *Init, + const PrintingPolicy &TypePP) { + if (Init->isAnyMemberInitializer()) + return Init->getAnyMember()->getName(); + if (Init->isBaseInitializer()) + return QualType(Init->getBaseClass(), 0).getAsString(TypePP); + if (Init->isDelegatingInitializer()) + return Init->getTypeSourceInfo()->getType().getAsString(TypePP); + llvm_unreachable("Unknown initializer type"); +} + +std::string SyntaxTree::Impl::getNodeValue(NodeId Id) const { + return getNodeValue(getNode(Id)); +} + +std::string SyntaxTree::Impl::getNodeValue(const Node &N) const { + const DynTypedNode &DTN = N.ASTNode; + if (auto *S = DTN.get<Stmt>()) + return getStmtValue(S); + if (auto *D = DTN.get<Decl>()) + return getDeclValue(D); + if (auto *Init = DTN.get<CXXCtorInitializer>()) + return getInitializerValue(Init, TypePP); + llvm_unreachable("Fatal: unhandled AST node.\n"); +} + +std::string SyntaxTree::Impl::getDeclValue(const Decl *D) const { + std::string Value; + if (auto *V = dyn_cast<ValueDecl>(D)) + return getRelativeName(V) + "(" + V->getType().getAsString(TypePP) + ")"; + if (auto *N = dyn_cast<NamedDecl>(D)) + Value += getRelativeName(N) + ";"; + if (auto *T = dyn_cast<TypedefNameDecl>(D)) + return Value + T->getUnderlyingType().getAsString(TypePP) + ";"; + if (auto *T = dyn_cast<TypeDecl>(D)) + if (T->getTypeForDecl()) + Value += + T->getTypeForDecl()->getCanonicalTypeInternal().getAsString(TypePP) + + ";"; + if (auto *U = dyn_cast<UsingDirectiveDecl>(D)) + return U->getNominatedNamespace()->getName(); + if (auto *A = dyn_cast<AccessSpecDecl>(D)) { + CharSourceRange Range(A->getSourceRange(), false); + return Lexer::getSourceText(Range, AST.getSourceManager(), + AST.getLangOpts()); + } + return Value; +} + +std::string SyntaxTree::Impl::getStmtValue(const Stmt *S) const { + if (auto *U = dyn_cast<UnaryOperator>(S)) + return UnaryOperator::getOpcodeStr(U->getOpcode()); + if (auto *B = dyn_cast<BinaryOperator>(S)) + return B->getOpcodeStr(); + if (auto *M = dyn_cast<MemberExpr>(S)) + return getRelativeName(M->getMemberDecl()); + if (auto *I = dyn_cast<IntegerLiteral>(S)) { + SmallString<256> Str; + I->getValue().toString(Str, /*Radix=*/10, /*Signed=*/false); + return Str.str(); + } + if (auto *F = dyn_cast<FloatingLiteral>(S)) { + SmallString<256> Str; + F->getValue().toString(Str); + return Str.str(); + } + if (auto *D = dyn_cast<DeclRefExpr>(S)) + return getRelativeName(D->getDecl(), getEnclosingDeclContext(AST, S)); + if (auto *String = dyn_cast<StringLiteral>(S)) + return String->getString(); + if (auto *B = dyn_cast<CXXBoolLiteralExpr>(S)) + return B->getValue() ? "true" : "false"; + return ""; +} + +/// Identifies a node in a subtree by its postorder offset, starting at 1. +struct SNodeId { + int Id = 0; + + explicit SNodeId(int Id) : Id(Id) {} + explicit SNodeId() = default; + + operator int() const { return Id; } + SNodeId &operator++() { return ++Id, *this; } + SNodeId &operator--() { return --Id, *this; } + SNodeId operator+(int Other) const { return SNodeId(Id + Other); } +}; + +class Subtree { +private: + /// The parent tree. + const SyntaxTree::Impl &Tree; + /// Maps SNodeIds to original ids. + std::vector<NodeId> RootIds; + /// Maps subtree nodes to their leftmost descendants wtihin the subtree. + std::vector<SNodeId> LeftMostDescendants; + +public: + std::vector<SNodeId> KeyRoots; + + Subtree(const SyntaxTree::Impl &Tree, NodeId SubtreeRoot) : Tree(Tree) { + RootIds = getSubtreePostorder(Tree, SubtreeRoot); + int NumLeaves = setLeftMostDescendants(); + computeKeyRoots(NumLeaves); + } + int getSize() const { return RootIds.size(); } + NodeId getIdInRoot(SNodeId Id) const { + assert(Id > 0 && Id <= getSize() && "Invalid subtree node index."); + return RootIds[Id - 1]; + } + const Node &getNode(SNodeId Id) const { + return Tree.getNode(getIdInRoot(Id)); + } + SNodeId getLeftMostDescendant(SNodeId Id) const { + assert(Id > 0 && Id <= getSize() && "Invalid subtree node index."); + return LeftMostDescendants[Id - 1]; + } + /// Returns the postorder index of the leftmost descendant in the subtree. + NodeId getPostorderOffset() const { + return Tree.PostorderIds[getIdInRoot(SNodeId(1))]; + } + std::string getNodeValue(SNodeId Id) const { + return Tree.getNodeValue(getIdInRoot(Id)); + } + +private: + /// Returns the number of leafs in the subtree. + int setLeftMostDescendants() { + int NumLeaves = 0; + LeftMostDescendants.resize(getSize()); + for (int I = 0; I < getSize(); ++I) { + SNodeId SI(I + 1); + const Node &N = getNode(SI); + NumLeaves += N.isLeaf(); + assert(I == Tree.PostorderIds[getIdInRoot(SI)] - getPostorderOffset() && + "Postorder traversal in subtree should correspond to traversal in " + "the root tree by a constant offset."); + LeftMostDescendants[I] = SNodeId(Tree.PostorderIds[N.LeftMostDescendant] - + getPostorderOffset()); + } + return NumLeaves; + } + void computeKeyRoots(int Leaves) { + KeyRoots.resize(Leaves); + std::unordered_set<int> Visited; + int K = Leaves - 1; + for (SNodeId I(getSize()); I > 0; --I) { + SNodeId LeftDesc = getLeftMostDescendant(I); + if (Visited.count(LeftDesc)) + continue; + assert(K >= 0 && "K should be non-negative"); + KeyRoots[K] = I; + Visited.insert(LeftDesc); + --K; + } + } +}; + +/// Implementation of Zhang and Shasha's Algorithm for tree edit distance. +/// Computes an optimal mapping between two trees using only insertion, +/// deletion and update as edit actions (similar to the Levenshtein distance). +class ZhangShashaMatcher { + const ASTDiff::Impl &DiffImpl; + Subtree S1; + Subtree S2; + std::unique_ptr<std::unique_ptr<double[]>[]> TreeDist, ForestDist; + +public: + ZhangShashaMatcher(const ASTDiff::Impl &DiffImpl, const SyntaxTree::Impl &T1, + const SyntaxTree::Impl &T2, NodeId Id1, NodeId Id2) + : DiffImpl(DiffImpl), S1(T1, Id1), S2(T2, Id2) { + TreeDist = llvm::make_unique<std::unique_ptr<double[]>[]>( + size_t(S1.getSize()) + 1); + ForestDist = llvm::make_unique<std::unique_ptr<double[]>[]>( + size_t(S1.getSize()) + 1); + for (int I = 0, E = S1.getSize() + 1; I < E; ++I) { + TreeDist[I] = llvm::make_unique<double[]>(size_t(S2.getSize()) + 1); + ForestDist[I] = llvm::make_unique<double[]>(size_t(S2.getSize()) + 1); + } + } + + std::vector<std::pair<NodeId, NodeId>> getMatchingNodes() { + std::vector<std::pair<NodeId, NodeId>> Matches; + std::vector<std::pair<SNodeId, SNodeId>> TreePairs; + + computeTreeDist(); + + bool RootNodePair = true; + + TreePairs.emplace_back(SNodeId(S1.getSize()), SNodeId(S2.getSize())); + + while (!TreePairs.empty()) { + SNodeId LastRow, LastCol, FirstRow, FirstCol, Row, Col; + std::tie(LastRow, LastCol) = TreePairs.back(); + TreePairs.pop_back(); + + if (!RootNodePair) { + computeForestDist(LastRow, LastCol); + } + + RootNodePair = false; + + FirstRow = S1.getLeftMostDescendant(LastRow); + FirstCol = S2.getLeftMostDescendant(LastCol); + + Row = LastRow; + Col = LastCol; + + while (Row > FirstRow || Col > FirstCol) { + if (Row > FirstRow && + ForestDist[Row - 1][Col] + 1 == ForestDist[Row][Col]) { + --Row; + } else if (Col > FirstCol && + ForestDist[Row][Col - 1] + 1 == ForestDist[Row][Col]) { + --Col; + } else { + SNodeId LMD1 = S1.getLeftMostDescendant(Row); + SNodeId LMD2 = S2.getLeftMostDescendant(Col); + if (LMD1 == S1.getLeftMostDescendant(LastRow) && + LMD2 == S2.getLeftMostDescendant(LastCol)) { + NodeId Id1 = S1.getIdInRoot(Row); + NodeId Id2 = S2.getIdInRoot(Col); + assert(DiffImpl.isMatchingPossible(Id1, Id2) && + "These nodes must not be matched."); + Matches.emplace_back(Id1, Id2); + --Row; + --Col; + } else { + TreePairs.emplace_back(Row, Col); + Row = LMD1; + Col = LMD2; + } + } + } + } + return Matches; + } + +private: + /// We use a simple cost model for edit actions, which seems good enough. + /// Simple cost model for edit actions. This seems to make the matching + /// algorithm perform reasonably well. + /// The values range between 0 and 1, or infinity if this edit action should + /// always be avoided. + static constexpr double DeletionCost = 1; + static constexpr double InsertionCost = 1; + + double getUpdateCost(SNodeId Id1, SNodeId Id2) { + if (!DiffImpl.isMatchingPossible(S1.getIdInRoot(Id1), S2.getIdInRoot(Id2))) + return std::numeric_limits<double>::max(); + return S1.getNodeValue(Id1) != S2.getNodeValue(Id2); + } + + void computeTreeDist() { + for (SNodeId Id1 : S1.KeyRoots) + for (SNodeId Id2 : S2.KeyRoots) + computeForestDist(Id1, Id2); + } + + void computeForestDist(SNodeId Id1, SNodeId Id2) { + assert(Id1 > 0 && Id2 > 0 && "Expecting offsets greater than 0."); + SNodeId LMD1 = S1.getLeftMostDescendant(Id1); + SNodeId LMD2 = S2.getLeftMostDescendant(Id2); + + ForestDist[LMD1][LMD2] = 0; + for (SNodeId D1 = LMD1 + 1; D1 <= Id1; ++D1) { + ForestDist[D1][LMD2] = ForestDist[D1 - 1][LMD2] + DeletionCost; + for (SNodeId D2 = LMD2 + 1; D2 <= Id2; ++D2) { + ForestDist[LMD1][D2] = ForestDist[LMD1][D2 - 1] + InsertionCost; + SNodeId DLMD1 = S1.getLeftMostDescendant(D1); + SNodeId DLMD2 = S2.getLeftMostDescendant(D2); + if (DLMD1 == LMD1 && DLMD2 == LMD2) { + double UpdateCost = getUpdateCost(D1, D2); + ForestDist[D1][D2] = + std::min({ForestDist[D1 - 1][D2] + DeletionCost, + ForestDist[D1][D2 - 1] + InsertionCost, + ForestDist[D1 - 1][D2 - 1] + UpdateCost}); + TreeDist[D1][D2] = ForestDist[D1][D2]; + } else { + ForestDist[D1][D2] = + std::min({ForestDist[D1 - 1][D2] + DeletionCost, + ForestDist[D1][D2 - 1] + InsertionCost, + ForestDist[DLMD1][DLMD2] + TreeDist[D1][D2]}); + } + } + } + } +}; + +ast_type_traits::ASTNodeKind Node::getType() const { + return ASTNode.getNodeKind(); +} + +StringRef Node::getTypeLabel() const { return getType().asStringRef(); } + +llvm::Optional<std::string> Node::getQualifiedIdentifier() const { + if (auto *ND = ASTNode.get<NamedDecl>()) { + if (ND->getDeclName().isIdentifier()) + return ND->getQualifiedNameAsString(); + } + return llvm::None; +} + +llvm::Optional<StringRef> Node::getIdentifier() const { + if (auto *ND = ASTNode.get<NamedDecl>()) { + if (ND->getDeclName().isIdentifier()) + return ND->getName(); + } + return llvm::None; +} + +namespace { +// Compares nodes by their depth. +struct HeightLess { + const SyntaxTree::Impl &Tree; + HeightLess(const SyntaxTree::Impl &Tree) : Tree(Tree) {} + bool operator()(NodeId Id1, NodeId Id2) const { + return Tree.getNode(Id1).Height < Tree.getNode(Id2).Height; + } +}; +} // end anonymous namespace + +namespace { +// Priority queue for nodes, sorted descendingly by their height. +class PriorityList { + const SyntaxTree::Impl &Tree; + HeightLess Cmp; + std::vector<NodeId> Container; + PriorityQueue<NodeId, std::vector<NodeId>, HeightLess> List; + +public: + PriorityList(const SyntaxTree::Impl &Tree) + : Tree(Tree), Cmp(Tree), List(Cmp, Container) {} + + void push(NodeId id) { List.push(id); } + + std::vector<NodeId> pop() { + int Max = peekMax(); + std::vector<NodeId> Result; + if (Max == 0) + return Result; + while (peekMax() == Max) { + Result.push_back(List.top()); + List.pop(); + } + // TODO this is here to get a stable output, not a good heuristic + llvm::sort(Result); + return Result; + } + int peekMax() const { + if (List.empty()) + return 0; + return Tree.getNode(List.top()).Height; + } + void open(NodeId Id) { + for (NodeId Child : Tree.getNode(Id).Children) + push(Child); + } +}; +} // end anonymous namespace + +bool ASTDiff::Impl::identical(NodeId Id1, NodeId Id2) const { + const Node &N1 = T1.getNode(Id1); + const Node &N2 = T2.getNode(Id2); + if (N1.Children.size() != N2.Children.size() || + !isMatchingPossible(Id1, Id2) || + T1.getNodeValue(Id1) != T2.getNodeValue(Id2)) + return false; + for (size_t Id = 0, E = N1.Children.size(); Id < E; ++Id) + if (!identical(N1.Children[Id], N2.Children[Id])) + return false; + return true; +} + +bool ASTDiff::Impl::isMatchingPossible(NodeId Id1, NodeId Id2) const { + return Options.isMatchingAllowed(T1.getNode(Id1), T2.getNode(Id2)); +} + +bool ASTDiff::Impl::haveSameParents(const Mapping &M, NodeId Id1, + NodeId Id2) const { + NodeId P1 = T1.getNode(Id1).Parent; + NodeId P2 = T2.getNode(Id2).Parent; + return (P1.isInvalid() && P2.isInvalid()) || + (P1.isValid() && P2.isValid() && M.getDst(P1) == P2); +} + +void ASTDiff::Impl::addOptimalMapping(Mapping &M, NodeId Id1, + NodeId Id2) const { + if (std::max(T1.getNumberOfDescendants(Id1), T2.getNumberOfDescendants(Id2)) > + Options.MaxSize) + return; + ZhangShashaMatcher Matcher(*this, T1, T2, Id1, Id2); + std::vector<std::pair<NodeId, NodeId>> R = Matcher.getMatchingNodes(); + for (const auto Tuple : R) { + NodeId Src = Tuple.first; + NodeId Dst = Tuple.second; + if (!M.hasSrc(Src) && !M.hasDst(Dst)) + M.link(Src, Dst); + } +} + +double ASTDiff::Impl::getJaccardSimilarity(const Mapping &M, NodeId Id1, + NodeId Id2) const { + int CommonDescendants = 0; + const Node &N1 = T1.getNode(Id1); + // Count the common descendants, excluding the subtree root. + for (NodeId Src = Id1 + 1; Src <= N1.RightMostDescendant; ++Src) { + NodeId Dst = M.getDst(Src); + CommonDescendants += int(Dst.isValid() && T2.isInSubtree(Dst, Id2)); + } + // We need to subtract 1 to get the number of descendants excluding the root. + double Denominator = T1.getNumberOfDescendants(Id1) - 1 + + T2.getNumberOfDescendants(Id2) - 1 - CommonDescendants; + // CommonDescendants is less than the size of one subtree. + assert(Denominator >= 0 && "Expected non-negative denominator."); + if (Denominator == 0) + return 0; + return CommonDescendants / Denominator; +} + +NodeId ASTDiff::Impl::findCandidate(const Mapping &M, NodeId Id1) const { + NodeId Candidate; + double HighestSimilarity = 0.0; + for (NodeId Id2 : T2) { + if (!isMatchingPossible(Id1, Id2)) + continue; + if (M.hasDst(Id2)) + continue; + double Similarity = getJaccardSimilarity(M, Id1, Id2); + if (Similarity >= Options.MinSimilarity && Similarity > HighestSimilarity) { + HighestSimilarity = Similarity; + Candidate = Id2; + } + } + return Candidate; +} + +void ASTDiff::Impl::matchBottomUp(Mapping &M) const { + std::vector<NodeId> Postorder = getSubtreePostorder(T1, T1.getRootId()); + for (NodeId Id1 : Postorder) { + if (Id1 == T1.getRootId() && !M.hasSrc(T1.getRootId()) && + !M.hasDst(T2.getRootId())) { + if (isMatchingPossible(T1.getRootId(), T2.getRootId())) { + M.link(T1.getRootId(), T2.getRootId()); + addOptimalMapping(M, T1.getRootId(), T2.getRootId()); + } + break; + } + bool Matched = M.hasSrc(Id1); + const Node &N1 = T1.getNode(Id1); + bool MatchedChildren = llvm::any_of( + N1.Children, [&](NodeId Child) { return M.hasSrc(Child); }); + if (Matched || !MatchedChildren) + continue; + NodeId Id2 = findCandidate(M, Id1); + if (Id2.isValid()) { + M.link(Id1, Id2); + addOptimalMapping(M, Id1, Id2); + } + } +} + +Mapping ASTDiff::Impl::matchTopDown() const { + PriorityList L1(T1); + PriorityList L2(T2); + + Mapping M(T1.getSize() + T2.getSize()); + + L1.push(T1.getRootId()); + L2.push(T2.getRootId()); + + int Max1, Max2; + while (std::min(Max1 = L1.peekMax(), Max2 = L2.peekMax()) > + Options.MinHeight) { + if (Max1 > Max2) { + for (NodeId Id : L1.pop()) + L1.open(Id); + continue; + } + if (Max2 > Max1) { + for (NodeId Id : L2.pop()) + L2.open(Id); + continue; + } + std::vector<NodeId> H1, H2; + H1 = L1.pop(); + H2 = L2.pop(); + for (NodeId Id1 : H1) { + for (NodeId Id2 : H2) { + if (identical(Id1, Id2) && !M.hasSrc(Id1) && !M.hasDst(Id2)) { + for (int I = 0, E = T1.getNumberOfDescendants(Id1); I < E; ++I) + M.link(Id1 + I, Id2 + I); + } + } + } + for (NodeId Id1 : H1) { + if (!M.hasSrc(Id1)) + L1.open(Id1); + } + for (NodeId Id2 : H2) { + if (!M.hasDst(Id2)) + L2.open(Id2); + } + } + return M; +} + +ASTDiff::Impl::Impl(SyntaxTree::Impl &T1, SyntaxTree::Impl &T2, + const ComparisonOptions &Options) + : T1(T1), T2(T2), Options(Options) { + computeMapping(); + computeChangeKinds(TheMapping); +} + +void ASTDiff::Impl::computeMapping() { + TheMapping = matchTopDown(); + if (Options.StopAfterTopDown) + return; + matchBottomUp(TheMapping); +} + +void ASTDiff::Impl::computeChangeKinds(Mapping &M) { + for (NodeId Id1 : T1) { + if (!M.hasSrc(Id1)) { + T1.getMutableNode(Id1).Change = Delete; + T1.getMutableNode(Id1).Shift -= 1; + } + } + for (NodeId Id2 : T2) { + if (!M.hasDst(Id2)) { + T2.getMutableNode(Id2).Change = Insert; + T2.getMutableNode(Id2).Shift -= 1; + } + } + for (NodeId Id1 : T1.NodesBfs) { + NodeId Id2 = M.getDst(Id1); + if (Id2.isInvalid()) + continue; + if (!haveSameParents(M, Id1, Id2) || + T1.findPositionInParent(Id1, true) != + T2.findPositionInParent(Id2, true)) { + T1.getMutableNode(Id1).Shift -= 1; + T2.getMutableNode(Id2).Shift -= 1; + } + } + for (NodeId Id2 : T2.NodesBfs) { + NodeId Id1 = M.getSrc(Id2); + if (Id1.isInvalid()) + continue; + Node &N1 = T1.getMutableNode(Id1); + Node &N2 = T2.getMutableNode(Id2); + if (Id1.isInvalid()) + continue; + if (!haveSameParents(M, Id1, Id2) || + T1.findPositionInParent(Id1, true) != + T2.findPositionInParent(Id2, true)) { + N1.Change = N2.Change = Move; + } + if (T1.getNodeValue(Id1) != T2.getNodeValue(Id2)) { + N1.Change = N2.Change = (N1.Change == Move ? UpdateMove : Update); + } + } +} + +ASTDiff::ASTDiff(SyntaxTree &T1, SyntaxTree &T2, + const ComparisonOptions &Options) + : DiffImpl(llvm::make_unique<Impl>(*T1.TreeImpl, *T2.TreeImpl, Options)) {} + +ASTDiff::~ASTDiff() = default; + +NodeId ASTDiff::getMapped(const SyntaxTree &SourceTree, NodeId Id) const { + return DiffImpl->getMapped(SourceTree.TreeImpl, Id); +} + +SyntaxTree::SyntaxTree(ASTContext &AST) + : TreeImpl(llvm::make_unique<SyntaxTree::Impl>( + this, AST.getTranslationUnitDecl(), AST)) {} + +SyntaxTree::~SyntaxTree() = default; + +const ASTContext &SyntaxTree::getASTContext() const { return TreeImpl->AST; } + +const Node &SyntaxTree::getNode(NodeId Id) const { + return TreeImpl->getNode(Id); +} + +int SyntaxTree::getSize() const { return TreeImpl->getSize(); } +NodeId SyntaxTree::getRootId() const { return TreeImpl->getRootId(); } +SyntaxTree::PreorderIterator SyntaxTree::begin() const { + return TreeImpl->begin(); +} +SyntaxTree::PreorderIterator SyntaxTree::end() const { return TreeImpl->end(); } + +int SyntaxTree::findPositionInParent(NodeId Id) const { + return TreeImpl->findPositionInParent(Id); +} + +std::pair<unsigned, unsigned> +SyntaxTree::getSourceRangeOffsets(const Node &N) const { + const SourceManager &SrcMgr = TreeImpl->AST.getSourceManager(); + SourceRange Range = N.ASTNode.getSourceRange(); + SourceLocation BeginLoc = Range.getBegin(); + SourceLocation EndLoc = Lexer::getLocForEndOfToken( + Range.getEnd(), /*Offset=*/0, SrcMgr, TreeImpl->AST.getLangOpts()); + if (auto *ThisExpr = N.ASTNode.get<CXXThisExpr>()) { + if (ThisExpr->isImplicit()) + EndLoc = BeginLoc; + } + unsigned Begin = SrcMgr.getFileOffset(SrcMgr.getExpansionLoc(BeginLoc)); + unsigned End = SrcMgr.getFileOffset(SrcMgr.getExpansionLoc(EndLoc)); + return {Begin, End}; +} + +std::string SyntaxTree::getNodeValue(NodeId Id) const { + return TreeImpl->getNodeValue(Id); +} + +std::string SyntaxTree::getNodeValue(const Node &N) const { + return TreeImpl->getNodeValue(N); +} + +} // end namespace diff +} // end namespace clang diff --git a/contrib/llvm/tools/clang/lib/Tooling/AllTUsExecution.cpp b/contrib/llvm/tools/clang/lib/Tooling/AllTUsExecution.cpp new file mode 100644 index 000000000000..0f172b782963 --- /dev/null +++ b/contrib/llvm/tools/clang/lib/Tooling/AllTUsExecution.cpp @@ -0,0 +1,187 @@ +//===- lib/Tooling/AllTUsExecution.cpp - Execute actions on all TUs. ------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "clang/Tooling/AllTUsExecution.h" +#include "clang/Tooling/ToolExecutorPluginRegistry.h" +#include "llvm/Support/ThreadPool.h" + +namespace clang { +namespace tooling { + +const char *AllTUsToolExecutor::ExecutorName = "AllTUsToolExecutor"; + +namespace { +llvm::Error make_string_error(const llvm::Twine &Message) { + return llvm::make_error<llvm::StringError>(Message, + llvm::inconvertibleErrorCode()); +} + +ArgumentsAdjuster getDefaultArgumentsAdjusters() { + return combineAdjusters( + getClangStripOutputAdjuster(), + combineAdjusters(getClangSyntaxOnlyAdjuster(), + getClangStripDependencyFileAdjuster())); +} + +class ThreadSafeToolResults : public ToolResults { +public: + void addResult(StringRef Key, StringRef Value) override { + std::unique_lock<std::mutex> LockGuard(Mutex); + Results.addResult(Key, Value); + } + + std::vector<std::pair<llvm::StringRef, llvm::StringRef>> + AllKVResults() override { + return Results.AllKVResults(); + } + + void forEachResult(llvm::function_ref<void(StringRef Key, StringRef Value)> + Callback) override { + Results.forEachResult(Callback); + } + +private: + InMemoryToolResults Results; + std::mutex Mutex; +}; + +} // namespace + +llvm::cl::opt<std::string> + Filter("filter", + llvm::cl::desc("Only process files that match this filter. " + "This flag only applies to all-TUs."), + llvm::cl::init(".*")); + +AllTUsToolExecutor::AllTUsToolExecutor( + const CompilationDatabase &Compilations, unsigned ThreadCount, + std::shared_ptr<PCHContainerOperations> PCHContainerOps) + : Compilations(Compilations), Results(new ThreadSafeToolResults), + Context(Results.get()), ThreadCount(ThreadCount) {} + +AllTUsToolExecutor::AllTUsToolExecutor( + CommonOptionsParser Options, unsigned ThreadCount, + std::shared_ptr<PCHContainerOperations> PCHContainerOps) + : OptionsParser(std::move(Options)), + Compilations(OptionsParser->getCompilations()), + Results(new ThreadSafeToolResults), Context(Results.get()), + ThreadCount(ThreadCount) {} + +llvm::Error AllTUsToolExecutor::execute( + llvm::ArrayRef< + std::pair<std::unique_ptr<FrontendActionFactory>, ArgumentsAdjuster>> + Actions) { + if (Actions.empty()) + return make_string_error("No action to execute."); + + if (Actions.size() != 1) + return make_string_error( + "Only support executing exactly 1 action at this point."); + + std::string ErrorMsg; + std::mutex TUMutex; + auto AppendError = [&](llvm::Twine Err) { + std::unique_lock<std::mutex> LockGuard(TUMutex); + ErrorMsg += Err.str(); + }; + + auto Log = [&](llvm::Twine Msg) { + std::unique_lock<std::mutex> LockGuard(TUMutex); + llvm::errs() << Msg.str() << "\n"; + }; + + std::vector<std::string> Files; + llvm::Regex RegexFilter(Filter); + for (const auto& File : Compilations.getAllFiles()) { + if (RegexFilter.match(File)) + Files.push_back(File); + } + // Add a counter to track the progress. + const std::string TotalNumStr = std::to_string(Files.size()); + unsigned Counter = 0; + auto Count = [&]() { + std::unique_lock<std::mutex> LockGuard(TUMutex); + return ++Counter; + }; + + auto &Action = Actions.front(); + + { + llvm::ThreadPool Pool(ThreadCount == 0 ? llvm::hardware_concurrency() + : ThreadCount); + llvm::SmallString<128> InitialWorkingDir; + if (auto EC = llvm::sys::fs::current_path(InitialWorkingDir)) { + InitialWorkingDir = ""; + llvm::errs() << "Error while getting current working directory: " + << EC.message() << "\n"; + } + for (std::string File : Files) { + Pool.async( + [&](std::string Path) { + Log("[" + std::to_string(Count()) + "/" + TotalNumStr + + "] Processing file " + Path); + ClangTool Tool(Compilations, {Path}); + Tool.appendArgumentsAdjuster(Action.second); + Tool.appendArgumentsAdjuster(getDefaultArgumentsAdjusters()); + for (const auto &FileAndContent : OverlayFiles) + Tool.mapVirtualFile(FileAndContent.first(), + FileAndContent.second); + // Do not restore working dir from multiple threads to avoid races. + Tool.setRestoreWorkingDir(false); + if (Tool.run(Action.first.get())) + AppendError(llvm::Twine("Failed to run action on ") + Path + + "\n"); + }, + File); + } + // Make sure all tasks have finished before resetting the working directory. + Pool.wait(); + if (!InitialWorkingDir.empty()) { + if (auto EC = llvm::sys::fs::set_current_path(InitialWorkingDir)) + llvm::errs() << "Error while restoring working directory: " + << EC.message() << "\n"; + } + } + + if (!ErrorMsg.empty()) + return make_string_error(ErrorMsg); + + return llvm::Error::success(); +} + +static llvm::cl::opt<unsigned> ExecutorConcurrency( + "execute-concurrency", + llvm::cl::desc("The number of threads used to process all files in " + "parallel. Set to 0 for hardware concurrency. " + "This flag only applies to all-TUs."), + llvm::cl::init(0)); + +class AllTUsToolExecutorPlugin : public ToolExecutorPlugin { +public: + llvm::Expected<std::unique_ptr<ToolExecutor>> + create(CommonOptionsParser &OptionsParser) override { + if (OptionsParser.getSourcePathList().empty()) + return make_string_error( + "[AllTUsToolExecutorPlugin] Please provide a directory/file path in " + "the compilation database."); + return llvm::make_unique<AllTUsToolExecutor>(std::move(OptionsParser), + ExecutorConcurrency); + } +}; + +static ToolExecutorPluginRegistry::Add<AllTUsToolExecutorPlugin> + X("all-TUs", "Runs FrontendActions on all TUs in the compilation database. " + "Tool results are stored in memory."); + +// This anchor is used to force the linker to link in the generated object file +// and thus register the plugin. +volatile int AllTUsToolExecutorAnchorSource = 0; + +} // end namespace tooling +} // end namespace clang diff --git a/contrib/llvm/tools/clang/lib/Tooling/ArgumentsAdjusters.cpp b/contrib/llvm/tools/clang/lib/Tooling/ArgumentsAdjusters.cpp new file mode 100644 index 000000000000..f5040b8a09d5 --- /dev/null +++ b/contrib/llvm/tools/clang/lib/Tooling/ArgumentsAdjusters.cpp @@ -0,0 +1,134 @@ +//===- ArgumentsAdjusters.cpp - Command line arguments adjuster -----------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// This file contains definitions of classes which implement ArgumentsAdjuster +// interface. +// +//===----------------------------------------------------------------------===// + +#include "clang/Tooling/ArgumentsAdjusters.h" +#include "clang/Basic/LLVM.h" +#include "llvm/ADT/StringRef.h" +#include <cstddef> + +namespace clang { +namespace tooling { + +/// Add -fsyntax-only option to the command line arguments. +ArgumentsAdjuster getClangSyntaxOnlyAdjuster() { + return [](const CommandLineArguments &Args, StringRef /*unused*/) { + CommandLineArguments AdjustedArgs; + for (size_t i = 0, e = Args.size(); i < e; ++i) { + StringRef Arg = Args[i]; + // FIXME: Remove options that generate output. + if (!Arg.startswith("-fcolor-diagnostics") && + !Arg.startswith("-fdiagnostics-color")) + AdjustedArgs.push_back(Args[i]); + } + AdjustedArgs.push_back("-fsyntax-only"); + return AdjustedArgs; + }; +} + +ArgumentsAdjuster getClangStripOutputAdjuster() { + return [](const CommandLineArguments &Args, StringRef /*unused*/) { + CommandLineArguments AdjustedArgs; + for (size_t i = 0, e = Args.size(); i < e; ++i) { + StringRef Arg = Args[i]; + if (!Arg.startswith("-o")) + AdjustedArgs.push_back(Args[i]); + + if (Arg == "-o") { + // Output is specified as -o foo. Skip the next argument too. + ++i; + } + // Else, the output is specified as -ofoo. Just do nothing. + } + return AdjustedArgs; + }; +} + +ArgumentsAdjuster getClangStripDependencyFileAdjuster() { + return [](const CommandLineArguments &Args, StringRef /*unused*/) { + CommandLineArguments AdjustedArgs; + for (size_t i = 0, e = Args.size(); i < e; ++i) { + StringRef Arg = Args[i]; + // All dependency-file options begin with -M. These include -MM, + // -MF, -MG, -MP, -MT, -MQ, -MD, and -MMD. + if (!Arg.startswith("-M")) { + AdjustedArgs.push_back(Args[i]); + continue; + } + + if (Arg == "-MF" || Arg == "-MT" || Arg == "-MQ") + // These flags take an argument: -MX foo. Skip the next argument also. + ++i; + } + return AdjustedArgs; + }; +} + +ArgumentsAdjuster getInsertArgumentAdjuster(const CommandLineArguments &Extra, + ArgumentInsertPosition Pos) { + return [Extra, Pos](const CommandLineArguments &Args, StringRef /*unused*/) { + CommandLineArguments Return(Args); + + CommandLineArguments::iterator I; + if (Pos == ArgumentInsertPosition::END) { + I = Return.end(); + } else { + I = Return.begin(); + ++I; // To leave the program name in place + } + + Return.insert(I, Extra.begin(), Extra.end()); + return Return; + }; +} + +ArgumentsAdjuster getInsertArgumentAdjuster(const char *Extra, + ArgumentInsertPosition Pos) { + return getInsertArgumentAdjuster(CommandLineArguments(1, Extra), Pos); +} + +ArgumentsAdjuster combineAdjusters(ArgumentsAdjuster First, + ArgumentsAdjuster Second) { + if (!First) + return Second; + if (!Second) + return First; + return [First, Second](const CommandLineArguments &Args, StringRef File) { + return Second(First(Args, File), File); + }; +} + +ArgumentsAdjuster getStripPluginsAdjuster() { + return [](const CommandLineArguments &Args, StringRef /*unused*/) { + CommandLineArguments AdjustedArgs; + for (size_t I = 0, E = Args.size(); I != E; I++) { + // According to https://clang.llvm.org/docs/ClangPlugins.html + // plugin arguments are in the form: + // -Xclang {-load, -plugin, -plugin-arg-<plugin-name>, -add-plugin} + // -Xclang <arbitrary-argument> + if (I + 4 < E && Args[I] == "-Xclang" && + (Args[I + 1] == "-load" || Args[I + 1] == "-plugin" || + llvm::StringRef(Args[I + 1]).startswith("-plugin-arg-") || + Args[I + 1] == "-add-plugin") && + Args[I + 2] == "-Xclang") { + I += 3; + continue; + } + AdjustedArgs.push_back(Args[I]); + } + return AdjustedArgs; + }; +} + +} // end namespace tooling +} // end namespace clang diff --git a/contrib/llvm/tools/clang/lib/Tooling/CommonOptionsParser.cpp b/contrib/llvm/tools/clang/lib/Tooling/CommonOptionsParser.cpp new file mode 100644 index 000000000000..74ad4e83ee3f --- /dev/null +++ b/contrib/llvm/tools/clang/lib/Tooling/CommonOptionsParser.cpp @@ -0,0 +1,180 @@ +//===--- CommonOptionsParser.cpp - common options for clang tools ---------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// This file implements the CommonOptionsParser class used to parse common +// command-line options for clang tools, so that they can be run as separate +// command-line applications with a consistent common interface for handling +// compilation database and input files. +// +// It provides a common subset of command-line options, common algorithm +// for locating a compilation database and source files, and help messages +// for the basic command-line interface. +// +// It creates a CompilationDatabase and reads common command-line options. +// +// This class uses the Clang Tooling infrastructure, see +// http://clang.llvm.org/docs/HowToSetupToolingForLLVM.html +// for details on setting it up with LLVM source tree. +// +//===----------------------------------------------------------------------===// + +#include "clang/Tooling/CommonOptionsParser.h" +#include "clang/Tooling/Tooling.h" +#include "llvm/Support/CommandLine.h" + +using namespace clang::tooling; +using namespace llvm; + +const char *const CommonOptionsParser::HelpMessage = + "\n" + "-p <build-path> is used to read a compile command database.\n" + "\n" + "\tFor example, it can be a CMake build directory in which a file named\n" + "\tcompile_commands.json exists (use -DCMAKE_EXPORT_COMPILE_COMMANDS=ON\n" + "\tCMake option to get this output). When no build path is specified,\n" + "\ta search for compile_commands.json will be attempted through all\n" + "\tparent paths of the first input file . See:\n" + "\thttp://clang.llvm.org/docs/HowToSetupToolingForLLVM.html for an\n" + "\texample of setting up Clang Tooling on a source tree.\n" + "\n" + "<source0> ... specify the paths of source files. These paths are\n" + "\tlooked up in the compile command database. If the path of a file is\n" + "\tabsolute, it needs to point into CMake's source tree. If the path is\n" + "\trelative, the current working directory needs to be in the CMake\n" + "\tsource tree and the file must be in a subdirectory of the current\n" + "\tworking directory. \"./\" prefixes in the relative files will be\n" + "\tautomatically removed, but the rest of a relative path must be a\n" + "\tsuffix of a path in the compile command database.\n" + "\n"; + +void ArgumentsAdjustingCompilations::appendArgumentsAdjuster( + ArgumentsAdjuster Adjuster) { + Adjusters.push_back(std::move(Adjuster)); +} + +std::vector<CompileCommand> ArgumentsAdjustingCompilations::getCompileCommands( + StringRef FilePath) const { + return adjustCommands(Compilations->getCompileCommands(FilePath)); +} + +std::vector<std::string> +ArgumentsAdjustingCompilations::getAllFiles() const { + return Compilations->getAllFiles(); +} + +std::vector<CompileCommand> +ArgumentsAdjustingCompilations::getAllCompileCommands() const { + return adjustCommands(Compilations->getAllCompileCommands()); +} + +std::vector<CompileCommand> ArgumentsAdjustingCompilations::adjustCommands( + std::vector<CompileCommand> Commands) const { + for (CompileCommand &Command : Commands) + for (const auto &Adjuster : Adjusters) + Command.CommandLine = Adjuster(Command.CommandLine, Command.Filename); + return Commands; +} + +llvm::Error CommonOptionsParser::init( + int &argc, const char **argv, cl::OptionCategory &Category, + llvm::cl::NumOccurrencesFlag OccurrencesFlag, const char *Overview) { + static cl::opt<bool> Help("h", cl::desc("Alias for -help"), cl::Hidden, + cl::sub(*cl::AllSubCommands)); + + static cl::opt<std::string> BuildPath("p", cl::desc("Build path"), + cl::Optional, cl::cat(Category), + cl::sub(*cl::AllSubCommands)); + + static cl::list<std::string> SourcePaths( + cl::Positional, cl::desc("<source0> [... <sourceN>]"), OccurrencesFlag, + cl::cat(Category), cl::sub(*cl::AllSubCommands)); + + static cl::list<std::string> ArgsAfter( + "extra-arg", + cl::desc("Additional argument to append to the compiler command line"), + cl::cat(Category), cl::sub(*cl::AllSubCommands)); + + static cl::list<std::string> ArgsBefore( + "extra-arg-before", + cl::desc("Additional argument to prepend to the compiler command line"), + cl::cat(Category), cl::sub(*cl::AllSubCommands)); + + cl::ResetAllOptionOccurrences(); + + cl::HideUnrelatedOptions(Category); + + std::string ErrorMessage; + Compilations = + FixedCompilationDatabase::loadFromCommandLine(argc, argv, ErrorMessage); + if (!ErrorMessage.empty()) + ErrorMessage.append("\n"); + llvm::raw_string_ostream OS(ErrorMessage); + // Stop initializing if command-line option parsing failed. + if (!cl::ParseCommandLineOptions(argc, argv, Overview, &OS)) { + OS.flush(); + return llvm::make_error<llvm::StringError>("[CommonOptionsParser]: " + + ErrorMessage, + llvm::inconvertibleErrorCode()); + } + + cl::PrintOptionValues(); + + SourcePathList = SourcePaths; + if ((OccurrencesFlag == cl::ZeroOrMore || OccurrencesFlag == cl::Optional) && + SourcePathList.empty()) + return llvm::Error::success(); + if (!Compilations) { + if (!BuildPath.empty()) { + Compilations = + CompilationDatabase::autoDetectFromDirectory(BuildPath, ErrorMessage); + } else { + Compilations = CompilationDatabase::autoDetectFromSource(SourcePaths[0], + ErrorMessage); + } + if (!Compilations) { + llvm::errs() << "Error while trying to load a compilation database:\n" + << ErrorMessage << "Running without flags.\n"; + Compilations.reset( + new FixedCompilationDatabase(".", std::vector<std::string>())); + } + } + auto AdjustingCompilations = + llvm::make_unique<ArgumentsAdjustingCompilations>( + std::move(Compilations)); + Adjuster = + getInsertArgumentAdjuster(ArgsBefore, ArgumentInsertPosition::BEGIN); + Adjuster = combineAdjusters( + std::move(Adjuster), + getInsertArgumentAdjuster(ArgsAfter, ArgumentInsertPosition::END)); + AdjustingCompilations->appendArgumentsAdjuster(Adjuster); + Compilations = std::move(AdjustingCompilations); + return llvm::Error::success(); +} + +llvm::Expected<CommonOptionsParser> CommonOptionsParser::create( + int &argc, const char **argv, llvm::cl::OptionCategory &Category, + llvm::cl::NumOccurrencesFlag OccurrencesFlag, const char *Overview) { + CommonOptionsParser Parser; + llvm::Error Err = + Parser.init(argc, argv, Category, OccurrencesFlag, Overview); + if (Err) + return std::move(Err); + return std::move(Parser); +} + +CommonOptionsParser::CommonOptionsParser( + int &argc, const char **argv, cl::OptionCategory &Category, + llvm::cl::NumOccurrencesFlag OccurrencesFlag, const char *Overview) { + llvm::Error Err = init(argc, argv, Category, OccurrencesFlag, Overview); + if (Err) { + llvm::report_fatal_error( + "CommonOptionsParser: failed to parse command-line arguments. " + + llvm::toString(std::move(Err))); + } +} diff --git a/contrib/llvm/tools/clang/lib/Tooling/CompilationDatabase.cpp b/contrib/llvm/tools/clang/lib/Tooling/CompilationDatabase.cpp new file mode 100644 index 000000000000..cce8e1f1df24 --- /dev/null +++ b/contrib/llvm/tools/clang/lib/Tooling/CompilationDatabase.cpp @@ -0,0 +1,421 @@ +//===- CompilationDatabase.cpp --------------------------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// This file contains implementations of the CompilationDatabase base class +// and the FixedCompilationDatabase. +// +// FIXME: Various functions that take a string &ErrorMessage should be upgraded +// to Expected. +// +//===----------------------------------------------------------------------===// + +#include "clang/Tooling/CompilationDatabase.h" +#include "clang/Basic/Diagnostic.h" +#include "clang/Basic/DiagnosticIDs.h" +#include "clang/Basic/DiagnosticOptions.h" +#include "clang/Basic/LLVM.h" +#include "clang/Driver/Action.h" +#include "clang/Driver/Compilation.h" +#include "clang/Driver/Driver.h" +#include "clang/Driver/DriverDiagnostic.h" +#include "clang/Driver/Job.h" +#include "clang/Frontend/TextDiagnosticPrinter.h" +#include "clang/Tooling/CompilationDatabasePluginRegistry.h" +#include "clang/Tooling/Tooling.h" +#include "llvm/ADT/ArrayRef.h" +#include "llvm/ADT/IntrusiveRefCntPtr.h" +#include "llvm/ADT/STLExtras.h" +#include "llvm/ADT/SmallString.h" +#include "llvm/ADT/SmallVector.h" +#include "llvm/ADT/StringRef.h" +#include "llvm/Option/Arg.h" +#include "llvm/Support/Casting.h" +#include "llvm/Support/Compiler.h" +#include "llvm/Support/ErrorOr.h" +#include "llvm/Support/Host.h" +#include "llvm/Support/LineIterator.h" +#include "llvm/Support/MemoryBuffer.h" +#include "llvm/Support/Path.h" +#include "llvm/Support/raw_ostream.h" +#include <algorithm> +#include <cassert> +#include <cstring> +#include <iterator> +#include <memory> +#include <sstream> +#include <string> +#include <system_error> +#include <utility> +#include <vector> + +using namespace clang; +using namespace tooling; + +LLVM_INSTANTIATE_REGISTRY(CompilationDatabasePluginRegistry) + +CompilationDatabase::~CompilationDatabase() = default; + +std::unique_ptr<CompilationDatabase> +CompilationDatabase::loadFromDirectory(StringRef BuildDirectory, + std::string &ErrorMessage) { + llvm::raw_string_ostream ErrorStream(ErrorMessage); + for (CompilationDatabasePluginRegistry::iterator + It = CompilationDatabasePluginRegistry::begin(), + Ie = CompilationDatabasePluginRegistry::end(); + It != Ie; ++It) { + std::string DatabaseErrorMessage; + std::unique_ptr<CompilationDatabasePlugin> Plugin(It->instantiate()); + if (std::unique_ptr<CompilationDatabase> DB = + Plugin->loadFromDirectory(BuildDirectory, DatabaseErrorMessage)) + return DB; + ErrorStream << It->getName() << ": " << DatabaseErrorMessage << "\n"; + } + return nullptr; +} + +static std::unique_ptr<CompilationDatabase> +findCompilationDatabaseFromDirectory(StringRef Directory, + std::string &ErrorMessage) { + std::stringstream ErrorStream; + bool HasErrorMessage = false; + while (!Directory.empty()) { + std::string LoadErrorMessage; + + if (std::unique_ptr<CompilationDatabase> DB = + CompilationDatabase::loadFromDirectory(Directory, LoadErrorMessage)) + return DB; + + if (!HasErrorMessage) { + ErrorStream << "No compilation database found in " << Directory.str() + << " or any parent directory\n" << LoadErrorMessage; + HasErrorMessage = true; + } + + Directory = llvm::sys::path::parent_path(Directory); + } + ErrorMessage = ErrorStream.str(); + return nullptr; +} + +std::unique_ptr<CompilationDatabase> +CompilationDatabase::autoDetectFromSource(StringRef SourceFile, + std::string &ErrorMessage) { + SmallString<1024> AbsolutePath(getAbsolutePath(SourceFile)); + StringRef Directory = llvm::sys::path::parent_path(AbsolutePath); + + std::unique_ptr<CompilationDatabase> DB = + findCompilationDatabaseFromDirectory(Directory, ErrorMessage); + + if (!DB) + ErrorMessage = ("Could not auto-detect compilation database for file \"" + + SourceFile + "\"\n" + ErrorMessage).str(); + return DB; +} + +std::unique_ptr<CompilationDatabase> +CompilationDatabase::autoDetectFromDirectory(StringRef SourceDir, + std::string &ErrorMessage) { + SmallString<1024> AbsolutePath(getAbsolutePath(SourceDir)); + + std::unique_ptr<CompilationDatabase> DB = + findCompilationDatabaseFromDirectory(AbsolutePath, ErrorMessage); + + if (!DB) + ErrorMessage = ("Could not auto-detect compilation database from directory \"" + + SourceDir + "\"\n" + ErrorMessage).str(); + return DB; +} + +std::vector<CompileCommand> CompilationDatabase::getAllCompileCommands() const { + std::vector<CompileCommand> Result; + for (const auto &File : getAllFiles()) { + auto C = getCompileCommands(File); + std::move(C.begin(), C.end(), std::back_inserter(Result)); + } + return Result; +} + +CompilationDatabasePlugin::~CompilationDatabasePlugin() = default; + +namespace { + +// Helper for recursively searching through a chain of actions and collecting +// all inputs, direct and indirect, of compile jobs. +struct CompileJobAnalyzer { + SmallVector<std::string, 2> Inputs; + + void run(const driver::Action *A) { + runImpl(A, false); + } + +private: + void runImpl(const driver::Action *A, bool Collect) { + bool CollectChildren = Collect; + switch (A->getKind()) { + case driver::Action::CompileJobClass: + CollectChildren = true; + break; + + case driver::Action::InputClass: + if (Collect) { + const auto *IA = cast<driver::InputAction>(A); + Inputs.push_back(IA->getInputArg().getSpelling()); + } + break; + + default: + // Don't care about others + break; + } + + for (const driver::Action *AI : A->inputs()) + runImpl(AI, CollectChildren); + } +}; + +// Special DiagnosticConsumer that looks for warn_drv_input_file_unused +// diagnostics from the driver and collects the option strings for those unused +// options. +class UnusedInputDiagConsumer : public DiagnosticConsumer { +public: + UnusedInputDiagConsumer(DiagnosticConsumer &Other) : Other(Other) {} + + void HandleDiagnostic(DiagnosticsEngine::Level DiagLevel, + const Diagnostic &Info) override { + if (Info.getID() == diag::warn_drv_input_file_unused) { + // Arg 1 for this diagnostic is the option that didn't get used. + UnusedInputs.push_back(Info.getArgStdStr(0)); + } else if (DiagLevel >= DiagnosticsEngine::Error) { + // If driver failed to create compilation object, show the diagnostics + // to user. + Other.HandleDiagnostic(DiagLevel, Info); + } + } + + DiagnosticConsumer &Other; + SmallVector<std::string, 2> UnusedInputs; +}; + +// Unary functor for asking "Given a StringRef S1, does there exist a string +// S2 in Arr where S1 == S2?" +struct MatchesAny { + MatchesAny(ArrayRef<std::string> Arr) : Arr(Arr) {} + + bool operator() (StringRef S) { + for (const std::string *I = Arr.begin(), *E = Arr.end(); I != E; ++I) + if (*I == S) + return true; + return false; + } + +private: + ArrayRef<std::string> Arr; +}; + +// Filter of tools unused flags such as -no-integrated-as and -Wa,*. +// They are not used for syntax checking, and could confuse targets +// which don't support these options. +struct FilterUnusedFlags { + bool operator() (StringRef S) { + return (S == "-no-integrated-as") || S.startswith("-Wa,"); + } +}; + +std::string GetClangToolCommand() { + static int Dummy; + std::string ClangExecutable = + llvm::sys::fs::getMainExecutable("clang", (void *)&Dummy); + SmallString<128> ClangToolPath; + ClangToolPath = llvm::sys::path::parent_path(ClangExecutable); + llvm::sys::path::append(ClangToolPath, "clang-tool"); + return ClangToolPath.str(); +} + +} // namespace + +/// Strips any positional args and possible argv[0] from a command-line +/// provided by the user to construct a FixedCompilationDatabase. +/// +/// FixedCompilationDatabase requires a command line to be in this format as it +/// constructs the command line for each file by appending the name of the file +/// to be compiled. FixedCompilationDatabase also adds its own argv[0] to the +/// start of the command line although its value is not important as it's just +/// ignored by the Driver invoked by the ClangTool using the +/// FixedCompilationDatabase. +/// +/// FIXME: This functionality should probably be made available by +/// clang::driver::Driver although what the interface should look like is not +/// clear. +/// +/// \param[in] Args Args as provided by the user. +/// \return Resulting stripped command line. +/// \li true if successful. +/// \li false if \c Args cannot be used for compilation jobs (e.g. +/// contains an option like -E or -version). +static bool stripPositionalArgs(std::vector<const char *> Args, + std::vector<std::string> &Result, + std::string &ErrorMsg) { + IntrusiveRefCntPtr<DiagnosticOptions> DiagOpts = new DiagnosticOptions(); + llvm::raw_string_ostream Output(ErrorMsg); + TextDiagnosticPrinter DiagnosticPrinter(Output, &*DiagOpts); + UnusedInputDiagConsumer DiagClient(DiagnosticPrinter); + DiagnosticsEngine Diagnostics( + IntrusiveRefCntPtr<DiagnosticIDs>(new DiagnosticIDs()), + &*DiagOpts, &DiagClient, false); + + // The clang executable path isn't required since the jobs the driver builds + // will not be executed. + std::unique_ptr<driver::Driver> NewDriver(new driver::Driver( + /* ClangExecutable= */ "", llvm::sys::getDefaultTargetTriple(), + Diagnostics)); + NewDriver->setCheckInputsExist(false); + + // This becomes the new argv[0]. The value is used to detect libc++ include + // dirs on Mac, it isn't used for other platforms. + std::string Argv0 = GetClangToolCommand(); + Args.insert(Args.begin(), Argv0.c_str()); + + // By adding -c, we force the driver to treat compilation as the last phase. + // It will then issue warnings via Diagnostics about un-used options that + // would have been used for linking. If the user provided a compiler name as + // the original argv[0], this will be treated as a linker input thanks to + // insertng a new argv[0] above. All un-used options get collected by + // UnusedInputdiagConsumer and get stripped out later. + Args.push_back("-c"); + + // Put a dummy C++ file on to ensure there's at least one compile job for the + // driver to construct. If the user specified some other argument that + // prevents compilation, e.g. -E or something like -version, we may still end + // up with no jobs but then this is the user's fault. + Args.push_back("placeholder.cpp"); + + Args.erase(std::remove_if(Args.begin(), Args.end(), FilterUnusedFlags()), + Args.end()); + + const std::unique_ptr<driver::Compilation> Compilation( + NewDriver->BuildCompilation(Args)); + if (!Compilation) + return false; + + const driver::JobList &Jobs = Compilation->getJobs(); + + CompileJobAnalyzer CompileAnalyzer; + + for (const auto &Cmd : Jobs) { + // Collect only for Assemble, Backend, and Compile jobs. If we do all jobs + // we get duplicates since Link jobs point to Assemble jobs as inputs. + // -flto* flags make the BackendJobClass, which still needs analyzer. + if (Cmd.getSource().getKind() == driver::Action::AssembleJobClass || + Cmd.getSource().getKind() == driver::Action::BackendJobClass || + Cmd.getSource().getKind() == driver::Action::CompileJobClass) { + CompileAnalyzer.run(&Cmd.getSource()); + } + } + + if (CompileAnalyzer.Inputs.empty()) { + ErrorMsg = "warning: no compile jobs found\n"; + return false; + } + + // Remove all compilation input files from the command line. This is + // necessary so that getCompileCommands() can construct a command line for + // each file. + std::vector<const char *>::iterator End = std::remove_if( + Args.begin(), Args.end(), MatchesAny(CompileAnalyzer.Inputs)); + + // Remove all inputs deemed unused for compilation. + End = std::remove_if(Args.begin(), End, MatchesAny(DiagClient.UnusedInputs)); + + // Remove the -c add above as well. It will be at the end right now. + assert(strcmp(*(End - 1), "-c") == 0); + --End; + + Result = std::vector<std::string>(Args.begin() + 1, End); + return true; +} + +std::unique_ptr<FixedCompilationDatabase> +FixedCompilationDatabase::loadFromCommandLine(int &Argc, + const char *const *Argv, + std::string &ErrorMsg, + Twine Directory) { + ErrorMsg.clear(); + if (Argc == 0) + return nullptr; + const char *const *DoubleDash = std::find(Argv, Argv + Argc, StringRef("--")); + if (DoubleDash == Argv + Argc) + return nullptr; + std::vector<const char *> CommandLine(DoubleDash + 1, Argv + Argc); + Argc = DoubleDash - Argv; + + std::vector<std::string> StrippedArgs; + if (!stripPositionalArgs(CommandLine, StrippedArgs, ErrorMsg)) + return nullptr; + return llvm::make_unique<FixedCompilationDatabase>(Directory, StrippedArgs); +} + +std::unique_ptr<FixedCompilationDatabase> +FixedCompilationDatabase::loadFromFile(StringRef Path, std::string &ErrorMsg) { + ErrorMsg.clear(); + llvm::ErrorOr<std::unique_ptr<llvm::MemoryBuffer>> File = + llvm::MemoryBuffer::getFile(Path); + if (std::error_code Result = File.getError()) { + ErrorMsg = "Error while opening fixed database: " + Result.message(); + return nullptr; + } + std::vector<std::string> Args{llvm::line_iterator(**File), + llvm::line_iterator()}; + return llvm::make_unique<FixedCompilationDatabase>( + llvm::sys::path::parent_path(Path), std::move(Args)); +} + +FixedCompilationDatabase:: +FixedCompilationDatabase(Twine Directory, ArrayRef<std::string> CommandLine) { + std::vector<std::string> ToolCommandLine(1, GetClangToolCommand()); + ToolCommandLine.insert(ToolCommandLine.end(), + CommandLine.begin(), CommandLine.end()); + CompileCommands.emplace_back(Directory, StringRef(), + std::move(ToolCommandLine), + StringRef()); +} + +std::vector<CompileCommand> +FixedCompilationDatabase::getCompileCommands(StringRef FilePath) const { + std::vector<CompileCommand> Result(CompileCommands); + Result[0].CommandLine.push_back(FilePath); + Result[0].Filename = FilePath; + return Result; +} + +namespace { + +class FixedCompilationDatabasePlugin : public CompilationDatabasePlugin { + std::unique_ptr<CompilationDatabase> + loadFromDirectory(StringRef Directory, std::string &ErrorMessage) override { + SmallString<1024> DatabasePath(Directory); + llvm::sys::path::append(DatabasePath, "compile_flags.txt"); + return FixedCompilationDatabase::loadFromFile(DatabasePath, ErrorMessage); + } +}; + +} // namespace + +static CompilationDatabasePluginRegistry::Add<FixedCompilationDatabasePlugin> +X("fixed-compilation-database", "Reads plain-text flags file"); + +namespace clang { +namespace tooling { + +// This anchor is used to force the linker to link in the generated object file +// and thus register the JSONCompilationDatabasePlugin. +extern volatile int JSONAnchorSource; +static int LLVM_ATTRIBUTE_UNUSED JSONAnchorDest = JSONAnchorSource; + +} // namespace tooling +} // namespace clang diff --git a/contrib/llvm/tools/clang/lib/Tooling/Core/Diagnostic.cpp b/contrib/llvm/tools/clang/lib/Tooling/Core/Diagnostic.cpp new file mode 100644 index 000000000000..e3a33d9a3755 --- /dev/null +++ b/contrib/llvm/tools/clang/lib/Tooling/Core/Diagnostic.cpp @@ -0,0 +1,51 @@ +//===--- Diagnostic.cpp - Framework for clang diagnostics tools ----------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// Implements classes to support/store diagnostics refactoring. +// +//===----------------------------------------------------------------------===// + +#include "clang/Tooling/Core/Diagnostic.h" +#include "clang/Basic/SourceManager.h" + +namespace clang { +namespace tooling { + +DiagnosticMessage::DiagnosticMessage(llvm::StringRef Message) + : Message(Message), FileOffset(0) {} + +DiagnosticMessage::DiagnosticMessage(llvm::StringRef Message, + const SourceManager &Sources, + SourceLocation Loc) + : Message(Message), FileOffset(0) { + assert(Loc.isValid() && Loc.isFileID()); + FilePath = Sources.getFilename(Loc); + + // Don't store offset in the scratch space. It doesn't tell anything to the + // user. Moreover, it depends on the history of macro expansions and thus + // prevents deduplication of warnings in headers. + if (!FilePath.empty()) + FileOffset = Sources.getFileOffset(Loc); +} + +Diagnostic::Diagnostic(llvm::StringRef DiagnosticName, + Diagnostic::Level DiagLevel, StringRef BuildDirectory) + : DiagnosticName(DiagnosticName), DiagLevel(DiagLevel), + BuildDirectory(BuildDirectory) {} + +Diagnostic::Diagnostic(llvm::StringRef DiagnosticName, + const DiagnosticMessage &Message, + const llvm::StringMap<Replacements> &Fix, + const SmallVector<DiagnosticMessage, 1> &Notes, + Level DiagLevel, llvm::StringRef BuildDirectory) + : DiagnosticName(DiagnosticName), Message(Message), Fix(Fix), Notes(Notes), + DiagLevel(DiagLevel), BuildDirectory(BuildDirectory) {} + +} // end namespace tooling +} // end namespace clang diff --git a/contrib/llvm/tools/clang/lib/Tooling/Core/Lookup.cpp b/contrib/llvm/tools/clang/lib/Tooling/Core/Lookup.cpp new file mode 100644 index 000000000000..cc448d144e2c --- /dev/null +++ b/contrib/llvm/tools/clang/lib/Tooling/Core/Lookup.cpp @@ -0,0 +1,191 @@ +//===--- Lookup.cpp - Framework for clang refactoring tools ---------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// This file defines helper methods for clang tools performing name lookup. +// +//===----------------------------------------------------------------------===// + +#include "clang/Tooling/Core/Lookup.h" +#include "clang/AST/Decl.h" +#include "clang/AST/DeclCXX.h" +#include "clang/AST/DeclarationName.h" +using namespace clang; +using namespace clang::tooling; + +// 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 (*FromIter == *UseIter) + return false; + // 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; + } + assert(FromIter == FromNamespaces.end() && UseIter == UseNamespaces.end()); + return false; +} + +static StringRef getBestNamespaceSubstr(const DeclContext *DeclA, + StringRef NewName, + bool HadLeadingColonColon) { + while (true) { + while (DeclA && !isa<NamespaceDecl>(DeclA)) + DeclA = DeclA->getParent(); + + // Fully qualified it is! Leave :: in place if it's there already. + if (!DeclA) + return HadLeadingColonColon ? NewName : NewName.substr(2); + + // Otherwise strip off redundant namespace qualifications from the new name. + // We use the fully qualified name of the namespace and remove that part + // from NewName if it has an identical prefix. + std::string NS = + "::" + cast<NamespaceDecl>(DeclA)->getQualifiedNameAsString() + "::"; + if (NewName.startswith(NS)) + return NewName.substr(NS.size()); + + // No match yet. Strip of a namespace from the end of the chain and try + // again. This allows to get optimal qualifications even if the old and new + // decl only share common namespaces at a higher level. + DeclA = DeclA->getParent(); + } +} + +/// Check if the name specifier begins with a written "::". +static bool isFullyQualified(const NestedNameSpecifier *NNS) { + while (NNS) { + if (NNS->getKind() == NestedNameSpecifier::Global) + return true; + NNS = NNS->getPrefix(); + } + return false; +} + +// Returns true if spelling symbol \p QName as \p Spelling in \p UseContext is +// ambiguous. For example, if QName is "::y::bar" and the spelling is "y::bar" +// in `UseContext` "a" that contains a nested namespace "a::y", then "y::bar" +// can be resolved to ::a::y::bar, which can cause compile error. +// FIXME: consider using namespaces. +static bool isAmbiguousNameInScope(StringRef Spelling, StringRef QName, + const DeclContext &UseContext) { + assert(QName.startswith("::")); + if (Spelling.startswith("::")) + return false; + + // Lookup the first component of Spelling in all enclosing namespaces and + // check if there is any existing symbols with the same name but in different + // scope. + StringRef Head = Spelling.split("::").first; + + llvm::SmallVector<const NamespaceDecl *, 4> UseNamespaces = + getAllNamedNamespaces(&UseContext); + auto &AST = UseContext.getParentASTContext(); + StringRef TrimmedQName = QName.substr(2); + for (const auto *NS : UseNamespaces) { + auto LookupRes = NS->lookup(DeclarationName(&AST.Idents.get(Head))); + if (!LookupRes.empty()) { + for (const NamedDecl *Res : LookupRes) + if (!TrimmedQName.startswith(Res->getQualifiedNameAsString())) + return true; + } + } + return false; +} + +std::string tooling::replaceNestedName(const NestedNameSpecifier *Use, + const DeclContext *UseContext, + const NamedDecl *FromDecl, + StringRef ReplacementString) { + assert(ReplacementString.startswith("::") && + "Expected fully-qualified name!"); + + // We can do a raw name replacement when we are not inside the namespace for + // 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 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()); + 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; + } + // We did not match this because of a using statement, so we will need to + // figure out how good a namespace match we have with our destination type. + // We work backwards (from most specific possible namespace to least + // specific). + StringRef Suggested = getBestNamespaceSubstr(UseContext, ReplacementString, + isFullyQualified(Use)); + // Use the fully qualified name if the suggested name is ambiguous. + // FIXME: consider re-shortening the name until the name is not ambiguous. We + // are not doing this because ambiguity is pretty bad and we should not try to + // be clever in handling such cases. Making this noticeable to users seems to + // be a better option. + return isAmbiguousNameInScope(Suggested, ReplacementString, *UseContext) + ? ReplacementString + : Suggested; +} diff --git a/contrib/llvm/tools/clang/lib/Tooling/Core/Replacement.cpp b/contrib/llvm/tools/clang/lib/Tooling/Core/Replacement.cpp new file mode 100644 index 000000000000..3b7e39814afa --- /dev/null +++ b/contrib/llvm/tools/clang/lib/Tooling/Core/Replacement.cpp @@ -0,0 +1,628 @@ +//===- Replacement.cpp - Framework for clang refactoring tools ------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// Implements classes to support/store refactorings. +// +//===----------------------------------------------------------------------===// + +#include "clang/Tooling/Core/Replacement.h" +#include "clang/Basic/Diagnostic.h" +#include "clang/Basic/DiagnosticIDs.h" +#include "clang/Basic/DiagnosticOptions.h" +#include "clang/Basic/FileManager.h" +#include "clang/Basic/FileSystemOptions.h" +#include "clang/Basic/SourceLocation.h" +#include "clang/Basic/SourceManager.h" +#include "clang/Lex/Lexer.h" +#include "clang/Rewrite/Core/RewriteBuffer.h" +#include "clang/Rewrite/Core/Rewriter.h" +#include "llvm/ADT/IntrusiveRefCntPtr.h" +#include "llvm/ADT/SmallPtrSet.h" +#include "llvm/ADT/StringRef.h" +#include "llvm/Support/Error.h" +#include "llvm/Support/ErrorHandling.h" +#include "llvm/Support/MemoryBuffer.h" +#include "llvm/Support/VirtualFileSystem.h" +#include "llvm/Support/raw_ostream.h" +#include <algorithm> +#include <cassert> +#include <limits> +#include <map> +#include <string> +#include <utility> +#include <vector> + +using namespace clang; +using namespace tooling; + +static const char * const InvalidLocation = ""; + +Replacement::Replacement() : FilePath(InvalidLocation) {} + +Replacement::Replacement(StringRef FilePath, unsigned Offset, unsigned Length, + StringRef ReplacementText) + : FilePath(FilePath), ReplacementRange(Offset, Length), + ReplacementText(ReplacementText) {} + +Replacement::Replacement(const SourceManager &Sources, SourceLocation Start, + unsigned Length, StringRef ReplacementText) { + setFromSourceLocation(Sources, Start, Length, ReplacementText); +} + +Replacement::Replacement(const SourceManager &Sources, + const CharSourceRange &Range, + StringRef ReplacementText, + const LangOptions &LangOpts) { + setFromSourceRange(Sources, Range, ReplacementText, LangOpts); +} + +bool Replacement::isApplicable() const { + return FilePath != InvalidLocation; +} + +bool Replacement::apply(Rewriter &Rewrite) const { + SourceManager &SM = Rewrite.getSourceMgr(); + const FileEntry *Entry = SM.getFileManager().getFile(FilePath); + if (!Entry) + return false; + + FileID ID = SM.getOrCreateFileID(Entry, SrcMgr::C_User); + const SourceLocation Start = + SM.getLocForStartOfFile(ID). + getLocWithOffset(ReplacementRange.getOffset()); + // ReplaceText returns false on success. + // ReplaceText only fails if the source location is not a file location, in + // which case we already returned false earlier. + bool RewriteSucceeded = !Rewrite.ReplaceText( + Start, ReplacementRange.getLength(), ReplacementText); + assert(RewriteSucceeded); + return RewriteSucceeded; +} + +std::string Replacement::toString() const { + std::string Result; + llvm::raw_string_ostream Stream(Result); + Stream << FilePath << ": " << ReplacementRange.getOffset() << ":+" + << ReplacementRange.getLength() << ":\"" << ReplacementText << "\""; + return Stream.str(); +} + +namespace clang { +namespace tooling { + +bool operator<(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(); + + if (LHS.getFilePath() != RHS.getFilePath()) + return LHS.getFilePath() < RHS.getFilePath(); + return LHS.getReplacementText() < RHS.getReplacementText(); +} + +bool operator==(const Replacement &LHS, const Replacement &RHS) { + return LHS.getOffset() == RHS.getOffset() && + LHS.getLength() == RHS.getLength() && + LHS.getFilePath() == RHS.getFilePath() && + LHS.getReplacementText() == RHS.getReplacementText(); +} + +} // namespace tooling +} // namespace clang + +void Replacement::setFromSourceLocation(const SourceManager &Sources, + SourceLocation Start, unsigned Length, + StringRef ReplacementText) { + const std::pair<FileID, unsigned> DecomposedLocation = + Sources.getDecomposedLoc(Start); + const FileEntry *Entry = Sources.getFileEntryForID(DecomposedLocation.first); + this->FilePath = Entry ? Entry->getName() : InvalidLocation; + this->ReplacementRange = Range(DecomposedLocation.second, Length); + this->ReplacementText = ReplacementText; +} + +// FIXME: This should go into the Lexer, but we need to figure out how +// to handle ranges for refactoring in general first - there is no obvious +// good way how to integrate this into the Lexer yet. +static int getRangeSize(const SourceManager &Sources, + const CharSourceRange &Range, + const LangOptions &LangOpts) { + SourceLocation SpellingBegin = Sources.getSpellingLoc(Range.getBegin()); + SourceLocation SpellingEnd = Sources.getSpellingLoc(Range.getEnd()); + std::pair<FileID, unsigned> Start = Sources.getDecomposedLoc(SpellingBegin); + std::pair<FileID, unsigned> End = Sources.getDecomposedLoc(SpellingEnd); + if (Start.first != End.first) return -1; + if (Range.isTokenRange()) + End.second += Lexer::MeasureTokenLength(SpellingEnd, Sources, LangOpts); + return End.second - Start.second; +} + +void Replacement::setFromSourceRange(const SourceManager &Sources, + const CharSourceRange &Range, + StringRef ReplacementText, + const LangOptions &LangOpts) { + setFromSourceLocation(Sources, Sources.getSpellingLoc(Range.getBegin()), + getRangeSize(Sources, Range, LangOpts), + ReplacementText); +} + +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()); +} + +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."); +} + +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; +} + +char ReplacementError::ID = 0; + +Replacements Replacements::getCanonicalReplacements() const { + std::vector<Replacement> NewReplaces; + // Merge adjacent replacements. + for (const auto &R : Replaces) { + if (NewReplaces.empty()) { + NewReplaces.push_back(R); + continue; + } + auto &Prev = NewReplaces.back(); + unsigned PrevEnd = Prev.getOffset() + Prev.getLength(); + if (PrevEnd < R.getOffset()) { + NewReplaces.push_back(R); + } else { + 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; + } + } + ReplacementsImpl NewReplacesImpl(NewReplaces.begin(), NewReplaces.end()); + return Replacements(NewReplacesImpl.begin(), NewReplacesImpl.end()); +} + +// `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 containing 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::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()); + + // Special-case header insertions. + if (R.getOffset() == std::numeric_limits<unsigned>::max()) { + Replaces.insert(R); + return llvm::Error::success(); + } + + // 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(); + } + + // `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(); + } + --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. +// +// Position projection: +// Offsets and lengths of the replacements can generally refer to two different +// coordinate spaces. Replacements from 'First' refer to the original text +// whereas replacements from 'Second' refer to the text after applying 'First'. +// +// MergedReplacement always operates in the coordinate space of the original +// text, i.e. transforms elements from 'Second' to take into account what was +// changed based on the elements from 'First'. +// +// We can correctly calculate this projection as we look at the replacements in +// order of strictly increasing offsets. +// +// Invariants: +// * We always merge elements from 'First' into elements from 'Second' and vice +// versa. Within each set, the replacements are non-overlapping. +// * We only extend to the right, i.e. merge elements with strictly increasing +// offsets. +class MergedReplacement { +public: + MergedReplacement(const Replacement &R, bool MergeSecond, int D) + : MergeSecond(MergeSecond), Delta(D), FilePath(R.getFilePath()), + Offset(R.getOffset() + (MergeSecond ? 0 : Delta)), Length(R.getLength()), + Text(R.getReplacementText()) { + Delta += MergeSecond ? 0 : Text.size() - Length; + DeltaFirst = MergeSecond ? Text.size() - Length : 0; + } + + // Merges the next element 'R' into this merged element. As we always merge + // from 'First' into 'Second' or vice versa, the MergedReplacement knows what + // set the next element is coming from. + void merge(const Replacement &R) { + if (MergeSecond) { + unsigned REnd = R.getOffset() + Delta + R.getLength(); + unsigned End = Offset + Text.size(); + if (REnd > End) { + Length += REnd - End; + MergeSecond = false; + } + StringRef TextRef = Text; + StringRef Head = TextRef.substr(0, R.getOffset() + Delta - Offset); + StringRef Tail = TextRef.substr(REnd - Offset); + Text = (Head + R.getReplacementText() + Tail).str(); + Delta += R.getReplacementText().size() - R.getLength(); + } else { + unsigned End = Offset + Length; + StringRef RText = R.getReplacementText(); + StringRef Tail = RText.substr(End - R.getOffset()); + Text = (Text + Tail).str(); + if (R.getOffset() + RText.size() > End) { + Length = R.getOffset() + R.getLength() - Offset; + MergeSecond = true; + } else { + Length += R.getLength() - RText.size(); + } + DeltaFirst += RText.size() - R.getLength(); + } + } + + // Returns 'true' if 'R' starts strictly after the MergedReplacement and thus + // doesn't need to be merged. + bool endsBefore(const Replacement &R) const { + if (MergeSecond) + return Offset + Text.size() < R.getOffset() + Delta; + return Offset + Length < R.getOffset(); + } + + // Returns 'true' if an element from the second set should be merged next. + bool mergeSecond() const { return MergeSecond; } + + int deltaFirst() const { return DeltaFirst; } + Replacement asReplacement() const { return {FilePath, Offset, Length, Text}; } + +private: + bool MergeSecond; + + // Amount of characters that elements from 'Second' need to be shifted by in + // order to refer to the original text. + int Delta; + + // Sum of all deltas (text-length - length) of elements from 'First' merged + // into this element. This is used to update 'Delta' once the + // MergedReplacement is completed. + int DeltaFirst; + + // Data of the actually merged replacement. FilePath and Offset aren't changed + // as the element is only extended to the right. + const StringRef FilePath; + const unsigned Offset; + unsigned Length; + std::string Text; +}; + +} // namespace + +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; + ReplacementsImpl Result; + + // Iterate over both sets and always add the next element (smallest total + // Offset) from either 'First' or 'Second'. Merge that element with + // subsequent replacements as long as they overlap. See more details in the + // comment on MergedReplacement. + for (auto FirstI = First.begin(), SecondI = Second.begin(); + FirstI != First.end() || SecondI != Second.end();) { + bool NextIsFirst = SecondI == Second.end() || + (FirstI != First.end() && + FirstI->getOffset() < SecondI->getOffset() + Delta); + MergedReplacement Merged(NextIsFirst ? *FirstI : *SecondI, NextIsFirst, + Delta); + ++(NextIsFirst ? FirstI : SecondI); + + while ((Merged.mergeSecond() && SecondI != Second.end()) || + (!Merged.mergeSecond() && FirstI != First.end())) { + auto &I = Merged.mergeSecond() ? SecondI : FirstI; + if (Merged.endsBefore(*I)) + break; + Merged.merge(*I); + ++I; + } + 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) { + llvm::sort(Ranges, [](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; +} + +namespace clang { +namespace tooling { + +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."); + llvm::consumeError(std::move(Err)); + } + return FakeReplaces.merge(Replaces).getAffectedRanges(); +} + +} // namespace tooling +} // namespace clang + +std::vector<Range> Replacements::getAffectedRanges() const { + std::vector<Range> ChangedRanges; + int Shift = 0; + for (const auto &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().empty()) + Position--; + } + break; + } + return Position + Offset; +} + +namespace clang { +namespace tooling { + +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<llvm::vfs::InMemoryFileSystem> InMemoryFileSystem( + new llvm::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; +} + +} // namespace tooling +} // namespace clang diff --git a/contrib/llvm/tools/clang/lib/Tooling/Execution.cpp b/contrib/llvm/tools/clang/lib/Tooling/Execution.cpp new file mode 100644 index 000000000000..9ddb18a57b46 --- /dev/null +++ b/contrib/llvm/tools/clang/lib/Tooling/Execution.cpp @@ -0,0 +1,108 @@ +//===- lib/Tooling/Execution.cpp - Implements tool execution framework. ---===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "clang/Tooling/Execution.h" +#include "clang/Tooling/ToolExecutorPluginRegistry.h" +#include "clang/Tooling/Tooling.h" + +LLVM_INSTANTIATE_REGISTRY(clang::tooling::ToolExecutorPluginRegistry) + +namespace clang { +namespace tooling { + +llvm::cl::opt<std::string> + ExecutorName("executor", llvm::cl::desc("The name of the executor to use."), + llvm::cl::init("standalone")); + +void InMemoryToolResults::addResult(StringRef Key, StringRef Value) { + KVResults.push_back({Strings.save(Key), Strings.save(Value)}); +} + +std::vector<std::pair<llvm::StringRef, llvm::StringRef>> +InMemoryToolResults::AllKVResults() { + return KVResults; +} + +void InMemoryToolResults::forEachResult( + llvm::function_ref<void(StringRef Key, StringRef Value)> Callback) { + for (const auto &KV : KVResults) { + Callback(KV.first, KV.second); + } +} + +void ExecutionContext::reportResult(StringRef Key, StringRef Value) { + Results->addResult(Key, Value); +} + +llvm::Error +ToolExecutor::execute(std::unique_ptr<FrontendActionFactory> Action) { + return execute(std::move(Action), ArgumentsAdjuster()); +} + +llvm::Error ToolExecutor::execute(std::unique_ptr<FrontendActionFactory> Action, + ArgumentsAdjuster Adjuster) { + std::vector< + std::pair<std::unique_ptr<FrontendActionFactory>, ArgumentsAdjuster>> + Actions; + Actions.emplace_back(std::move(Action), std::move(Adjuster)); + return execute(Actions); +} + +namespace internal { +llvm::Expected<std::unique_ptr<ToolExecutor>> +createExecutorFromCommandLineArgsImpl(int &argc, const char **argv, + llvm::cl::OptionCategory &Category, + const char *Overview) { + auto OptionsParser = + CommonOptionsParser::create(argc, argv, Category, llvm::cl::ZeroOrMore, + /*Overview=*/Overview); + if (!OptionsParser) + return OptionsParser.takeError(); + for (auto I = ToolExecutorPluginRegistry::begin(), + E = ToolExecutorPluginRegistry::end(); + I != E; ++I) { + if (I->getName() != ExecutorName) { + continue; + } + std::unique_ptr<ToolExecutorPlugin> Plugin(I->instantiate()); + llvm::Expected<std::unique_ptr<ToolExecutor>> Executor = + Plugin->create(*OptionsParser); + if (!Executor) { + return llvm::make_error<llvm::StringError>( + llvm::Twine("Failed to create '") + I->getName() + + "': " + llvm::toString(Executor.takeError()) + "\n", + llvm::inconvertibleErrorCode()); + } + return std::move(*Executor); + } + return llvm::make_error<llvm::StringError>( + llvm::Twine("Executor \"") + ExecutorName + "\" is not registered.", + llvm::inconvertibleErrorCode()); +} +} // end namespace internal + +llvm::Expected<std::unique_ptr<ToolExecutor>> +createExecutorFromCommandLineArgs(int &argc, const char **argv, + llvm::cl::OptionCategory &Category, + const char *Overview) { + return internal::createExecutorFromCommandLineArgsImpl(argc, argv, Category, + Overview); +} + +// This anchor is used to force the linker to link in the generated object file +// and thus register the StandaloneToolExecutorPlugin etc. +extern volatile int StandaloneToolExecutorAnchorSource; +extern volatile int AllTUsToolExecutorAnchorSource; +static int LLVM_ATTRIBUTE_UNUSED StandaloneToolExecutorAnchorDest = + StandaloneToolExecutorAnchorSource; +static int LLVM_ATTRIBUTE_UNUSED AllTUsToolExecutorAnchorDest = + AllTUsToolExecutorAnchorSource; + +} // end namespace tooling +} // end namespace clang diff --git a/contrib/llvm/tools/clang/lib/Tooling/FileMatchTrie.cpp b/contrib/llvm/tools/clang/lib/Tooling/FileMatchTrie.cpp new file mode 100644 index 000000000000..202b3f00f3fb --- /dev/null +++ b/contrib/llvm/tools/clang/lib/Tooling/FileMatchTrie.cpp @@ -0,0 +1,196 @@ +//===- FileMatchTrie.cpp --------------------------------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// This file contains the implementation of a FileMatchTrie. +// +//===----------------------------------------------------------------------===// + +#include "clang/Tooling/FileMatchTrie.h" +#include "llvm/ADT/StringMap.h" +#include "llvm/ADT/StringRef.h" +#include "llvm/Support/FileSystem.h" +#include "llvm/Support/Path.h" +#include "llvm/Support/raw_ostream.h" +#include <string> +#include <vector> + +using namespace clang; +using namespace tooling; + +namespace { + +/// Default \c PathComparator using \c llvm::sys::fs::equivalent(). +struct DefaultPathComparator : public PathComparator { + bool equivalent(StringRef FileA, StringRef FileB) const override { + return FileA == FileB || llvm::sys::fs::equivalent(FileA, FileB); + } +}; + +} // namespace + +namespace clang { +namespace tooling { + +/// A node of the \c FileMatchTrie. +/// +/// Each node has storage for up to one path and a map mapping a path segment to +/// child nodes. The trie starts with an empty root node. +class FileMatchTrieNode { +public: + /// Inserts 'NewPath' into this trie. \c ConsumedLength denotes + /// the number of \c NewPath's trailing characters already consumed during + /// recursion. + /// + /// An insert of a path + /// 'p'starts at the root node and does the following: + /// - If the node is empty, insert 'p' into its storage and abort. + /// - If the node has a path 'p2' but no children, take the last path segment + /// 's' of 'p2', put a new child into the map at 's' an insert the rest of + /// 'p2' there. + /// - Insert a new child for the last segment of 'p' and insert the rest of + /// 'p' there. + /// + /// An insert operation is linear in the number of a path's segments. + void insert(StringRef NewPath, unsigned ConsumedLength = 0) { + // We cannot put relative paths into the FileMatchTrie as then a path can be + // a postfix of another path, violating a core assumption of the trie. + if (llvm::sys::path::is_relative(NewPath)) + return; + if (Path.empty()) { + // This is an empty leaf. Store NewPath and return. + Path = NewPath; + return; + } + if (Children.empty()) { + // This is a leaf, ignore duplicate entry if 'Path' equals 'NewPath'. + if (NewPath == Path) + return; + // Make this a node and create a child-leaf with 'Path'. + StringRef Element(llvm::sys::path::filename( + StringRef(Path).drop_back(ConsumedLength))); + Children[Element].Path = Path; + } + StringRef Element(llvm::sys::path::filename( + StringRef(NewPath).drop_back(ConsumedLength))); + Children[Element].insert(NewPath, ConsumedLength + Element.size() + 1); + } + + /// Tries to find the node under this \c FileMatchTrieNode that best + /// matches 'FileName'. + /// + /// If multiple paths fit 'FileName' equally well, \c IsAmbiguous is set to + /// \c true and an empty string is returned. If no path fits 'FileName', an + /// empty string is returned. \c ConsumedLength denotes the number of + /// \c Filename's trailing characters already consumed during recursion. + /// + /// To find the best matching node for a given path 'p', the + /// \c findEquivalent() function is called recursively for each path segment + /// (back to front) of 'p' until a node 'n' is reached that does not .. + /// - .. have children. In this case it is checked + /// whether the stored path is equivalent to 'p'. If yes, the best match is + /// found. Otherwise continue with the parent node as if this node did not + /// exist. + /// - .. a child matching the next path segment. In this case, all children of + /// 'n' are an equally good match for 'p'. All children are of 'n' are found + /// recursively and their equivalence to 'p' is determined. If none are + /// equivalent, continue with the parent node as if 'n' didn't exist. If one + /// is equivalent, the best match is found. Otherwise, report and ambigiuity + /// error. + StringRef findEquivalent(const PathComparator& Comparator, + StringRef FileName, + bool &IsAmbiguous, + unsigned ConsumedLength = 0) const { + if (Children.empty()) { + if (Comparator.equivalent(StringRef(Path), FileName)) + return StringRef(Path); + return {}; + } + StringRef Element(llvm::sys::path::filename(FileName.drop_back( + ConsumedLength))); + llvm::StringMap<FileMatchTrieNode>::const_iterator MatchingChild = + Children.find(Element); + if (MatchingChild != Children.end()) { + StringRef Result = MatchingChild->getValue().findEquivalent( + Comparator, FileName, IsAmbiguous, + ConsumedLength + Element.size() + 1); + if (!Result.empty() || IsAmbiguous) + return Result; + } + std::vector<StringRef> AllChildren; + getAll(AllChildren, MatchingChild); + StringRef Result; + for (const auto &Child : AllChildren) { + if (Comparator.equivalent(Child, FileName)) { + if (Result.empty()) { + Result = Child; + } else { + IsAmbiguous = true; + return {}; + } + } + } + return Result; + } + +private: + /// Gets all paths under this FileMatchTrieNode. + void getAll(std::vector<StringRef> &Results, + llvm::StringMap<FileMatchTrieNode>::const_iterator Except) const { + if (Path.empty()) + return; + if (Children.empty()) { + Results.push_back(StringRef(Path)); + return; + } + for (llvm::StringMap<FileMatchTrieNode>::const_iterator + It = Children.begin(), E = Children.end(); + It != E; ++It) { + if (It == Except) + continue; + It->getValue().getAll(Results, Children.end()); + } + } + + // The stored absolute path in this node. Only valid for leaf nodes, i.e. + // nodes where Children.empty(). + std::string Path; + + // The children of this node stored in a map based on the next path segment. + llvm::StringMap<FileMatchTrieNode> Children; +}; + +} // namespace tooling +} // namespace clang + +FileMatchTrie::FileMatchTrie() + : Root(new FileMatchTrieNode), Comparator(new DefaultPathComparator()) {} + +FileMatchTrie::FileMatchTrie(PathComparator *Comparator) + : Root(new FileMatchTrieNode), Comparator(Comparator) {} + +FileMatchTrie::~FileMatchTrie() { + delete Root; +} + +void FileMatchTrie::insert(StringRef NewPath) { + Root->insert(NewPath); +} + +StringRef FileMatchTrie::findEquivalent(StringRef FileName, + raw_ostream &Error) const { + if (llvm::sys::path::is_relative(FileName)) { + Error << "Cannot resolve relative paths"; + return {}; + } + bool IsAmbiguous = false; + StringRef Result = Root->findEquivalent(*Comparator, FileName, IsAmbiguous); + if (IsAmbiguous) + Error << "Path is ambiguous"; + return Result; +} diff --git a/contrib/llvm/tools/clang/lib/Tooling/FixIt.cpp b/contrib/llvm/tools/clang/lib/Tooling/FixIt.cpp new file mode 100644 index 000000000000..70942c5ac845 --- /dev/null +++ b/contrib/llvm/tools/clang/lib/Tooling/FixIt.cpp @@ -0,0 +1,31 @@ +//===--- FixIt.cpp - FixIt Hint utilities -----------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// This file contains implementations of utitilies to ease source code rewriting +// by providing helper functions related to FixItHint. +// +//===----------------------------------------------------------------------===// +#include "clang/Tooling/FixIt.h" +#include "clang/Lex/Lexer.h" + +namespace clang { +namespace tooling { +namespace fixit { + +namespace internal { +StringRef getText(SourceRange Range, const ASTContext &Context) { + return Lexer::getSourceText(CharSourceRange::getTokenRange(Range), + Context.getSourceManager(), + Context.getLangOpts()); +} +} // end namespace internal + +} // end namespace fixit +} // end namespace tooling +} // end namespace clang diff --git a/contrib/llvm/tools/clang/lib/Tooling/Inclusions/HeaderIncludes.cpp b/contrib/llvm/tools/clang/lib/Tooling/Inclusions/HeaderIncludes.cpp new file mode 100644 index 000000000000..c74ad0b9cd56 --- /dev/null +++ b/contrib/llvm/tools/clang/lib/Tooling/Inclusions/HeaderIncludes.cpp @@ -0,0 +1,330 @@ +//===--- HeaderIncludes.cpp - Insert/Delete #includes --*- C++ -*----------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "clang/Tooling/Inclusions/HeaderIncludes.h" +#include "clang/Basic/SourceManager.h" +#include "clang/Lex/Lexer.h" +#include "llvm/Support/FormatVariadic.h" + +namespace clang { +namespace tooling { +namespace { + +LangOptions createLangOpts() { + LangOptions LangOpts; + LangOpts.CPlusPlus = 1; + LangOpts.CPlusPlus11 = 1; + LangOpts.CPlusPlus14 = 1; + LangOpts.LineComment = 1; + LangOpts.CXXOperatorNames = 1; + LangOpts.Bool = 1; + LangOpts.ObjC = 1; + LangOpts.MicrosoftExt = 1; // To get kw___try, kw___finally. + LangOpts.DeclSpecKeyword = 1; // To get __declspec. + LangOpts.WChar = 1; // To get wchar_t + return LangOpts; +} + +// Returns the offset after skipping a sequence of tokens, matched by \p +// GetOffsetAfterSequence, from the start of the code. +// \p GetOffsetAfterSequence should be a function that matches a sequence of +// tokens and returns an offset after the sequence. +unsigned getOffsetAfterTokenSequence( + StringRef FileName, StringRef Code, const IncludeStyle &Style, + llvm::function_ref<unsigned(const SourceManager &, Lexer &, Token &)> + GetOffsetAfterSequence) { + SourceManagerForFile VirtualSM(FileName, Code); + SourceManager &SM = VirtualSM.get(); + Lexer Lex(SM.getMainFileID(), SM.getBuffer(SM.getMainFileID()), SM, + createLangOpts()); + Token Tok; + // Get the first token. + Lex.LexFromRawLexer(Tok); + return GetOffsetAfterSequence(SM, Lex, Tok); +} + +// Check if a sequence of tokens is like "#<Name> <raw_identifier>". If it is, +// \p Tok will be the token after this directive; otherwise, it can be any token +// after the given \p Tok (including \p Tok). +bool checkAndConsumeDirectiveWithName(Lexer &Lex, StringRef Name, Token &Tok) { + bool Matched = Tok.is(tok::hash) && !Lex.LexFromRawLexer(Tok) && + Tok.is(tok::raw_identifier) && + Tok.getRawIdentifier() == Name && !Lex.LexFromRawLexer(Tok) && + Tok.is(tok::raw_identifier); + if (Matched) + Lex.LexFromRawLexer(Tok); + return Matched; +} + +void skipComments(Lexer &Lex, Token &Tok) { + while (Tok.is(tok::comment)) + if (Lex.LexFromRawLexer(Tok)) + return; +} + +// Returns the offset after header guard directives and any comments +// before/after header guards. If no header guard presents in the code, this +// will returns the offset after skipping all comments from the start of the +// code. +unsigned getOffsetAfterHeaderGuardsAndComments(StringRef FileName, + StringRef Code, + const IncludeStyle &Style) { + return getOffsetAfterTokenSequence( + FileName, Code, Style, + [](const SourceManager &SM, Lexer &Lex, Token Tok) { + skipComments(Lex, Tok); + unsigned InitialOffset = SM.getFileOffset(Tok.getLocation()); + if (checkAndConsumeDirectiveWithName(Lex, "ifndef", Tok)) { + skipComments(Lex, Tok); + if (checkAndConsumeDirectiveWithName(Lex, "define", Tok)) + return SM.getFileOffset(Tok.getLocation()); + } + return InitialOffset; + }); +} + +// Check if a sequence of tokens is like +// "#include ("header.h" | <header.h>)". +// If it is, \p Tok will be the token after this directive; otherwise, it can be +// any token after the given \p Tok (including \p Tok). +bool checkAndConsumeInclusiveDirective(Lexer &Lex, Token &Tok) { + auto Matched = [&]() { + Lex.LexFromRawLexer(Tok); + return true; + }; + if (Tok.is(tok::hash) && !Lex.LexFromRawLexer(Tok) && + Tok.is(tok::raw_identifier) && Tok.getRawIdentifier() == "include") { + if (Lex.LexFromRawLexer(Tok)) + return false; + if (Tok.is(tok::string_literal)) + return Matched(); + if (Tok.is(tok::less)) { + while (!Lex.LexFromRawLexer(Tok) && Tok.isNot(tok::greater)) { + } + if (Tok.is(tok::greater)) + return Matched(); + } + } + return false; +} + +// Returns the offset of the last #include directive after which a new +// #include can be inserted. This ignores #include's after the #include block(s) +// in the beginning of a file to avoid inserting headers into code sections +// where new #include's should not be added by default. +// These code sections include: +// - raw string literals (containing #include). +// - #if blocks. +// - Special #include's among declarations (e.g. functions). +// +// If no #include after which a new #include can be inserted, this returns the +// offset after skipping all comments from the start of the code. +// Inserting after an #include is not allowed if it comes after code that is not +// #include (e.g. pre-processing directive that is not #include, declarations). +unsigned getMaxHeaderInsertionOffset(StringRef FileName, StringRef Code, + const IncludeStyle &Style) { + return getOffsetAfterTokenSequence( + FileName, Code, Style, + [](const SourceManager &SM, Lexer &Lex, Token Tok) { + skipComments(Lex, Tok); + unsigned MaxOffset = SM.getFileOffset(Tok.getLocation()); + while (checkAndConsumeInclusiveDirective(Lex, Tok)) + MaxOffset = SM.getFileOffset(Tok.getLocation()); + return MaxOffset; + }); +} + +inline StringRef trimInclude(StringRef IncludeName) { + return IncludeName.trim("\"<>"); +} + +const char IncludeRegexPattern[] = + R"(^[\t\ ]*#[\t\ ]*(import|include)[^"<]*(["<][^">]*[">]))"; + +} // anonymous namespace + +IncludeCategoryManager::IncludeCategoryManager(const IncludeStyle &Style, + StringRef FileName) + : Style(Style), FileName(FileName) { + FileStem = llvm::sys::path::stem(FileName); + for (const auto &Category : Style.IncludeCategories) + CategoryRegexs.emplace_back(Category.Regex, llvm::Regex::IgnoreCase); + IsMainFile = FileName.endswith(".c") || FileName.endswith(".cc") || + FileName.endswith(".cpp") || FileName.endswith(".c++") || + FileName.endswith(".cxx") || FileName.endswith(".m") || + FileName.endswith(".mm"); +} + +int IncludeCategoryManager::getIncludePriority(StringRef IncludeName, + bool CheckMainHeader) const { + int Ret = INT_MAX; + for (unsigned i = 0, e = CategoryRegexs.size(); i != e; ++i) + if (CategoryRegexs[i].match(IncludeName)) { + Ret = Style.IncludeCategories[i].Priority; + break; + } + if (CheckMainHeader && IsMainFile && Ret > 0 && isMainHeader(IncludeName)) + Ret = 0; + return Ret; +} + +bool IncludeCategoryManager::isMainHeader(StringRef IncludeName) const { + if (!IncludeName.startswith("\"")) + return false; + StringRef HeaderStem = + llvm::sys::path::stem(IncludeName.drop_front(1).drop_back(1)); + if (FileStem.startswith(HeaderStem) || + FileStem.startswith_lower(HeaderStem)) { + llvm::Regex MainIncludeRegex(HeaderStem.str() + Style.IncludeIsMainRegex, + llvm::Regex::IgnoreCase); + if (MainIncludeRegex.match(FileStem)) + return true; + } + return false; +} + +HeaderIncludes::HeaderIncludes(StringRef FileName, StringRef Code, + const IncludeStyle &Style) + : FileName(FileName), Code(Code), FirstIncludeOffset(-1), + MinInsertOffset( + getOffsetAfterHeaderGuardsAndComments(FileName, Code, Style)), + MaxInsertOffset(MinInsertOffset + + getMaxHeaderInsertionOffset( + FileName, Code.drop_front(MinInsertOffset), Style)), + Categories(Style, FileName), + IncludeRegex(llvm::Regex(IncludeRegexPattern)) { + // Add 0 for main header and INT_MAX for headers that are not in any + // category. + Priorities = {0, INT_MAX}; + for (const auto &Category : Style.IncludeCategories) + Priorities.insert(Category.Priority); + SmallVector<StringRef, 32> Lines; + Code.drop_front(MinInsertOffset).split(Lines, "\n"); + + unsigned Offset = MinInsertOffset; + unsigned NextLineOffset; + SmallVector<StringRef, 4> Matches; + for (auto Line : Lines) { + NextLineOffset = std::min(Code.size(), Offset + Line.size() + 1); + if (IncludeRegex.match(Line, &Matches)) { + // If this is the last line without trailing newline, we need to make + // sure we don't delete across the file boundary. + addExistingInclude( + Include(Matches[2], + tooling::Range( + Offset, std::min(Line.size() + 1, Code.size() - Offset))), + NextLineOffset); + } + Offset = NextLineOffset; + } + + // Populate CategoryEndOfssets: + // - Ensure that CategoryEndOffset[Highest] is always populated. + // - If CategoryEndOffset[Priority] isn't set, use the next higher value + // that is set, up to CategoryEndOffset[Highest]. + auto Highest = Priorities.begin(); + if (CategoryEndOffsets.find(*Highest) == CategoryEndOffsets.end()) { + if (FirstIncludeOffset >= 0) + CategoryEndOffsets[*Highest] = FirstIncludeOffset; + else + CategoryEndOffsets[*Highest] = MinInsertOffset; + } + // By this point, CategoryEndOffset[Highest] is always set appropriately: + // - to an appropriate location before/after existing #includes, or + // - to right after the header guard, or + // - to the beginning of the file. + for (auto I = ++Priorities.begin(), E = Priorities.end(); I != E; ++I) + if (CategoryEndOffsets.find(*I) == CategoryEndOffsets.end()) + CategoryEndOffsets[*I] = CategoryEndOffsets[*std::prev(I)]; +} + +// \p Offset: the start of the line following this include directive. +void HeaderIncludes::addExistingInclude(Include IncludeToAdd, + unsigned NextLineOffset) { + auto Iter = + ExistingIncludes.try_emplace(trimInclude(IncludeToAdd.Name)).first; + Iter->second.push_back(std::move(IncludeToAdd)); + auto &CurInclude = Iter->second.back(); + // The header name with quotes or angle brackets. + // Only record the offset of current #include if we can insert after it. + if (CurInclude.R.getOffset() <= MaxInsertOffset) { + int Priority = Categories.getIncludePriority( + CurInclude.Name, /*CheckMainHeader=*/FirstIncludeOffset < 0); + CategoryEndOffsets[Priority] = NextLineOffset; + IncludesByPriority[Priority].push_back(&CurInclude); + if (FirstIncludeOffset < 0) + FirstIncludeOffset = CurInclude.R.getOffset(); + } +} + +llvm::Optional<tooling::Replacement> +HeaderIncludes::insert(llvm::StringRef IncludeName, bool IsAngled) const { + assert(IncludeName == trimInclude(IncludeName)); + // If a <header> ("header") already exists in code, "header" (<header>) with + // different quotation will still be inserted. + // FIXME: figure out if this is the best behavior. + auto It = ExistingIncludes.find(IncludeName); + if (It != ExistingIncludes.end()) + for (const auto &Inc : It->second) + if ((IsAngled && StringRef(Inc.Name).startswith("<")) || + (!IsAngled && StringRef(Inc.Name).startswith("\""))) + return llvm::None; + std::string Quoted = + llvm::formatv(IsAngled ? "<{0}>" : "\"{0}\"", IncludeName); + StringRef QuotedName = Quoted; + int Priority = Categories.getIncludePriority( + QuotedName, /*CheckMainHeader=*/FirstIncludeOffset < 0); + auto CatOffset = CategoryEndOffsets.find(Priority); + assert(CatOffset != CategoryEndOffsets.end()); + unsigned InsertOffset = CatOffset->second; // Fall back offset + auto Iter = IncludesByPriority.find(Priority); + if (Iter != IncludesByPriority.end()) { + for (const auto *Inc : Iter->second) { + if (QuotedName < Inc->Name) { + InsertOffset = Inc->R.getOffset(); + break; + } + } + } + assert(InsertOffset <= Code.size()); + std::string NewInclude = llvm::formatv("#include {0}\n", QuotedName); + // When inserting headers at end of the code, also append '\n' to the code + // if it does not end with '\n'. + // FIXME: when inserting multiple #includes at the end of code, only one + // newline should be added. + if (InsertOffset == Code.size() && (!Code.empty() && Code.back() != '\n')) + NewInclude = "\n" + NewInclude; + return tooling::Replacement(FileName, InsertOffset, 0, NewInclude); +} + +tooling::Replacements HeaderIncludes::remove(llvm::StringRef IncludeName, + bool IsAngled) const { + assert(IncludeName == trimInclude(IncludeName)); + tooling::Replacements Result; + auto Iter = ExistingIncludes.find(IncludeName); + if (Iter == ExistingIncludes.end()) + return Result; + for (const auto &Inc : Iter->second) { + if ((IsAngled && StringRef(Inc.Name).startswith("\"")) || + (!IsAngled && StringRef(Inc.Name).startswith("<"))) + continue; + llvm::Error Err = Result.add(tooling::Replacement( + FileName, Inc.R.getOffset(), Inc.R.getLength(), "")); + if (Err) { + auto ErrMsg = "Unexpected conflicts in #include deletions: " + + llvm::toString(std::move(Err)); + llvm_unreachable(ErrMsg.c_str()); + } + } + return Result; +} + + +} // namespace tooling +} // namespace clang diff --git a/contrib/llvm/tools/clang/lib/Tooling/Inclusions/IncludeStyle.cpp b/contrib/llvm/tools/clang/lib/Tooling/Inclusions/IncludeStyle.cpp new file mode 100644 index 000000000000..3597710f1f6e --- /dev/null +++ b/contrib/llvm/tools/clang/lib/Tooling/Inclusions/IncludeStyle.cpp @@ -0,0 +1,31 @@ +//===--- IncludeStyle.cpp - Style of C++ #include directives -----*- C++-*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "clang/Tooling/Inclusions/IncludeStyle.h" + +using clang::tooling::IncludeStyle; + +namespace llvm { +namespace yaml { + +void MappingTraits<IncludeStyle::IncludeCategory>::mapping( + IO &IO, IncludeStyle::IncludeCategory &Category) { + IO.mapOptional("Regex", Category.Regex); + IO.mapOptional("Priority", Category.Priority); +} + +void ScalarEnumerationTraits<IncludeStyle::IncludeBlocksStyle>::enumeration( + IO &IO, IncludeStyle::IncludeBlocksStyle &Value) { + IO.enumCase(Value, "Preserve", IncludeStyle::IBS_Preserve); + IO.enumCase(Value, "Merge", IncludeStyle::IBS_Merge); + IO.enumCase(Value, "Regroup", IncludeStyle::IBS_Regroup); +} + +} // namespace yaml +} // namespace llvm diff --git a/contrib/llvm/tools/clang/lib/Tooling/InterpolatingCompilationDatabase.cpp b/contrib/llvm/tools/clang/lib/Tooling/InterpolatingCompilationDatabase.cpp new file mode 100644 index 000000000000..4d0d84f660a2 --- /dev/null +++ b/contrib/llvm/tools/clang/lib/Tooling/InterpolatingCompilationDatabase.cpp @@ -0,0 +1,547 @@ +//===- InterpolatingCompilationDatabase.cpp ---------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// InterpolatingCompilationDatabase wraps another CompilationDatabase and +// attempts to heuristically determine appropriate compile commands for files +// that are not included, such as headers or newly created files. +// +// Motivating cases include: +// Header files that live next to their implementation files. These typically +// share a base filename. (libclang/CXString.h, libclang/CXString.cpp). +// Some projects separate headers from includes. Filenames still typically +// match, maybe other path segments too. (include/llvm/IR/Use.h, lib/IR/Use.cc). +// Matches are sometimes only approximate (Sema.h, SemaDecl.cpp). This goes +// for directories too (Support/Unix/Process.inc, lib/Support/Process.cpp). +// Even if we can't find a "right" compile command, even a random one from +// the project will tend to get important flags like -I and -x right. +// +// We "borrow" the compile command for the closest available file: +// - points are awarded if the filename matches (ignoring extension) +// - points are awarded if the directory structure matches +// - ties are broken by length of path prefix match +// +// The compile command is adjusted, replacing the filename and removing output +// file arguments. The -x and -std flags may be affected too. +// +// Source language is a tricky issue: is it OK to use a .c file's command +// for building a .cc file? What language is a .h file in? +// - We only consider compile commands for c-family languages as candidates. +// - For files whose language is implied by the filename (e.g. .m, .hpp) +// we prefer candidates from the same language. +// If we must cross languages, we drop any -x and -std flags. +// - For .h files, candidates from any c-family language are acceptable. +// We use the candidate's language, inserting e.g. -x c++-header. +// +// This class is only useful when wrapping databases that can enumerate all +// their compile commands. If getAllFilenames() is empty, no inference occurs. +// +//===----------------------------------------------------------------------===// + +#include "clang/Driver/Options.h" +#include "clang/Driver/Types.h" +#include "clang/Frontend/LangStandard.h" +#include "clang/Tooling/CompilationDatabase.h" +#include "llvm/ADT/DenseMap.h" +#include "llvm/ADT/Optional.h" +#include "llvm/ADT/StringExtras.h" +#include "llvm/ADT/StringSwitch.h" +#include "llvm/Option/ArgList.h" +#include "llvm/Option/OptTable.h" +#include "llvm/Support/Debug.h" +#include "llvm/Support/Path.h" +#include "llvm/Support/StringSaver.h" +#include "llvm/Support/raw_ostream.h" +#include <memory> + +namespace clang { +namespace tooling { +namespace { +using namespace llvm; +namespace types = clang::driver::types; +namespace path = llvm::sys::path; + +// The length of the prefix these two strings have in common. +size_t matchingPrefix(StringRef L, StringRef R) { + size_t Limit = std::min(L.size(), R.size()); + for (size_t I = 0; I < Limit; ++I) + if (L[I] != R[I]) + return I; + return Limit; +} + +// A comparator for searching SubstringWithIndexes with std::equal_range etc. +// Optionaly prefix semantics: compares equal if the key is a prefix. +template <bool Prefix> struct Less { + bool operator()(StringRef Key, std::pair<StringRef, size_t> Value) const { + StringRef V = Prefix ? Value.first.substr(0, Key.size()) : Value.first; + return Key < V; + } + bool operator()(std::pair<StringRef, size_t> Value, StringRef Key) const { + StringRef V = Prefix ? Value.first.substr(0, Key.size()) : Value.first; + return V < Key; + } +}; + +// Infer type from filename. If we might have gotten it wrong, set *Certain. +// *.h will be inferred as a C header, but not certain. +types::ID guessType(StringRef Filename, bool *Certain = nullptr) { + // path::extension is ".cpp", lookupTypeForExtension wants "cpp". + auto Lang = + types::lookupTypeForExtension(path::extension(Filename).substr(1)); + if (Certain) + *Certain = Lang != types::TY_CHeader && Lang != types::TY_INVALID; + return Lang; +} + +// Return Lang as one of the canonical supported types. +// e.g. c-header --> c; fortran --> TY_INVALID +static types::ID foldType(types::ID Lang) { + switch (Lang) { + case types::TY_C: + case types::TY_CHeader: + return types::TY_C; + case types::TY_ObjC: + case types::TY_ObjCHeader: + return types::TY_ObjC; + case types::TY_CXX: + case types::TY_CXXHeader: + return types::TY_CXX; + case types::TY_ObjCXX: + case types::TY_ObjCXXHeader: + return types::TY_ObjCXX; + default: + return types::TY_INVALID; + } +} + +// A CompileCommand that can be applied to another file. +struct TransferableCommand { + // Flags that should not apply to all files are stripped from CommandLine. + CompileCommand Cmd; + // Language detected from -x or the filename. Never TY_INVALID. + Optional<types::ID> Type; + // Standard specified by -std. + LangStandard::Kind Std = LangStandard::lang_unspecified; + // Whether the command line is for the cl-compatible driver. + bool ClangCLMode; + + TransferableCommand(CompileCommand C) + : Cmd(std::move(C)), Type(guessType(Cmd.Filename)), + ClangCLMode(checkIsCLMode(Cmd.CommandLine)) { + std::vector<std::string> OldArgs = std::move(Cmd.CommandLine); + Cmd.CommandLine.clear(); + + // Wrap the old arguments in an InputArgList. + llvm::opt::InputArgList ArgList; + { + SmallVector<const char *, 16> TmpArgv; + for (const std::string &S : OldArgs) + TmpArgv.push_back(S.c_str()); + ArgList = {TmpArgv.begin(), TmpArgv.end()}; + } + + // Parse the old args in order to strip out and record unwanted flags. + // We parse each argument individually so that we can retain the exact + // spelling of each argument; re-rendering is lossy for aliased flags. + // E.g. in CL mode, /W4 maps to -Wall. + auto OptTable = clang::driver::createDriverOptTable(); + Cmd.CommandLine.emplace_back(OldArgs.front()); + for (unsigned Pos = 1; Pos < OldArgs.size();) { + using namespace driver::options; + + const unsigned OldPos = Pos; + std::unique_ptr<llvm::opt::Arg> Arg(OptTable->ParseOneArg( + ArgList, Pos, + /* Include */ClangCLMode ? CoreOption | CLOption : 0, + /* Exclude */ClangCLMode ? 0 : CLOption)); + + if (!Arg) + continue; + + const llvm::opt::Option &Opt = Arg->getOption(); + + // Strip input and output files. + if (Opt.matches(OPT_INPUT) || Opt.matches(OPT_o) || + (ClangCLMode && (Opt.matches(OPT__SLASH_Fa) || + Opt.matches(OPT__SLASH_Fe) || + Opt.matches(OPT__SLASH_Fi) || + Opt.matches(OPT__SLASH_Fo)))) + continue; + + // Strip -x, but record the overridden language. + if (const auto GivenType = tryParseTypeArg(*Arg)) { + Type = *GivenType; + continue; + } + + // Strip -std, but record the value. + if (const auto GivenStd = tryParseStdArg(*Arg)) { + if (*GivenStd != LangStandard::lang_unspecified) + Std = *GivenStd; + continue; + } + + Cmd.CommandLine.insert(Cmd.CommandLine.end(), + OldArgs.data() + OldPos, OldArgs.data() + Pos); + } + + if (Std != LangStandard::lang_unspecified) // -std take precedence over -x + Type = toType(LangStandard::getLangStandardForKind(Std).getLanguage()); + Type = foldType(*Type); + // The contract is to store None instead of TY_INVALID. + if (Type == types::TY_INVALID) + Type = llvm::None; + } + + // Produce a CompileCommand for \p filename, based on this one. + CompileCommand transferTo(StringRef Filename) const { + CompileCommand Result = Cmd; + Result.Filename = Filename; + bool TypeCertain; + auto TargetType = guessType(Filename, &TypeCertain); + // If the filename doesn't determine the language (.h), transfer with -x. + if (TargetType != types::TY_INVALID && !TypeCertain && Type) { + TargetType = types::onlyPrecompileType(TargetType) // header? + ? types::lookupHeaderTypeForSourceType(*Type) + : *Type; + if (ClangCLMode) { + const StringRef Flag = toCLFlag(TargetType); + if (!Flag.empty()) + Result.CommandLine.push_back(Flag); + } else { + Result.CommandLine.push_back("-x"); + Result.CommandLine.push_back(types::getTypeName(TargetType)); + } + } + // --std flag may only be transferred if the language is the same. + // We may consider "translating" these, e.g. c++11 -> c11. + if (Std != LangStandard::lang_unspecified && foldType(TargetType) == Type) { + Result.CommandLine.emplace_back(( + llvm::Twine(ClangCLMode ? "/std:" : "-std=") + + LangStandard::getLangStandardForKind(Std).getName()).str()); + } + Result.CommandLine.push_back(Filename); + return Result; + } + +private: + // Determine whether the given command line is intended for the CL driver. + static bool checkIsCLMode(ArrayRef<std::string> CmdLine) { + // First look for --driver-mode. + for (StringRef S : llvm::reverse(CmdLine)) { + if (S.consume_front("--driver-mode=")) + return S == "cl"; + } + + // Otherwise just check the clang executable file name. + return llvm::sys::path::stem(CmdLine.front()).endswith_lower("cl"); + } + + // Map the language from the --std flag to that of the -x flag. + static types::ID toType(InputKind::Language Lang) { + switch (Lang) { + case InputKind::C: + return types::TY_C; + case InputKind::CXX: + return types::TY_CXX; + case InputKind::ObjC: + return types::TY_ObjC; + case InputKind::ObjCXX: + return types::TY_ObjCXX; + default: + return types::TY_INVALID; + } + } + + // Convert a file type to the matching CL-style type flag. + static StringRef toCLFlag(types::ID Type) { + switch (Type) { + case types::TY_C: + case types::TY_CHeader: + return "/TC"; + case types::TY_CXX: + case types::TY_CXXHeader: + return "/TP"; + default: + return StringRef(); + } + } + + // Try to interpret the argument as a type specifier, e.g. '-x'. + Optional<types::ID> tryParseTypeArg(const llvm::opt::Arg &Arg) { + const llvm::opt::Option &Opt = Arg.getOption(); + using namespace driver::options; + if (ClangCLMode) { + if (Opt.matches(OPT__SLASH_TC) || Opt.matches(OPT__SLASH_Tc)) + return types::TY_C; + if (Opt.matches(OPT__SLASH_TP) || Opt.matches(OPT__SLASH_Tp)) + return types::TY_CXX; + } else { + if (Opt.matches(driver::options::OPT_x)) + return types::lookupTypeForTypeSpecifier(Arg.getValue()); + } + return None; + } + + // Try to interpret the argument as '-std='. + Optional<LangStandard::Kind> tryParseStdArg(const llvm::opt::Arg &Arg) { + using namespace driver::options; + if (Arg.getOption().matches(ClangCLMode ? OPT__SLASH_std : OPT_std_EQ)) { + return llvm::StringSwitch<LangStandard::Kind>(Arg.getValue()) +#define LANGSTANDARD(id, name, lang, ...) .Case(name, LangStandard::lang_##id) +#define LANGSTANDARD_ALIAS(id, alias) .Case(alias, LangStandard::lang_##id) +#include "clang/Frontend/LangStandards.def" +#undef LANGSTANDARD_ALIAS +#undef LANGSTANDARD + .Default(LangStandard::lang_unspecified); + } + return None; + } +}; + +// Given a filename, FileIndex picks the best matching file from the underlying +// DB. This is the proxy file whose CompileCommand will be reused. The +// heuristics incorporate file name, extension, and directory structure. +// Strategy: +// - Build indexes of each of the substrings we want to look up by. +// These indexes are just sorted lists of the substrings. +// - Each criterion corresponds to a range lookup into the index, so we only +// need O(log N) string comparisons to determine scores. +// +// Apart from path proximity signals, also takes file extensions into account +// when scoring the candidates. +class FileIndex { +public: + FileIndex(std::vector<std::string> Files) + : OriginalPaths(std::move(Files)), Strings(Arena) { + // Sort commands by filename for determinism (index is a tiebreaker later). + llvm::sort(OriginalPaths); + Paths.reserve(OriginalPaths.size()); + Types.reserve(OriginalPaths.size()); + Stems.reserve(OriginalPaths.size()); + for (size_t I = 0; I < OriginalPaths.size(); ++I) { + StringRef Path = Strings.save(StringRef(OriginalPaths[I]).lower()); + + Paths.emplace_back(Path, I); + Types.push_back(foldType(guessType(Path))); + Stems.emplace_back(sys::path::stem(Path), I); + auto Dir = ++sys::path::rbegin(Path), DirEnd = sys::path::rend(Path); + for (int J = 0; J < DirectorySegmentsIndexed && Dir != DirEnd; ++J, ++Dir) + if (Dir->size() > ShortDirectorySegment) // not trivial ones + Components.emplace_back(*Dir, I); + } + llvm::sort(Paths); + llvm::sort(Stems); + llvm::sort(Components); + } + + bool empty() const { return Paths.empty(); } + + // Returns the path for the file that best fits OriginalFilename. + // Candidates with extensions matching PreferLanguage will be chosen over + // others (unless it's TY_INVALID, or all candidates are bad). + StringRef chooseProxy(StringRef OriginalFilename, + types::ID PreferLanguage) const { + assert(!empty() && "need at least one candidate!"); + std::string Filename = OriginalFilename.lower(); + auto Candidates = scoreCandidates(Filename); + std::pair<size_t, int> Best = + pickWinner(Candidates, Filename, PreferLanguage); + + DEBUG_WITH_TYPE( + "interpolate", + llvm::dbgs() << "interpolate: chose " << OriginalPaths[Best.first] + << " as proxy for " << OriginalFilename << " preferring " + << (PreferLanguage == types::TY_INVALID + ? "none" + : types::getTypeName(PreferLanguage)) + << " score=" << Best.second << "\n"); + return OriginalPaths[Best.first]; + } + +private: + using SubstringAndIndex = std::pair<StringRef, size_t>; + // Directory matching parameters: we look at the last two segments of the + // parent directory (usually the semantically significant ones in practice). + // We search only the last four of each candidate (for efficiency). + constexpr static int DirectorySegmentsIndexed = 4; + constexpr static int DirectorySegmentsQueried = 2; + constexpr static int ShortDirectorySegment = 1; // Only look at longer names. + + // Award points to candidate entries that should be considered for the file. + // Returned keys are indexes into paths, and the values are (nonzero) scores. + DenseMap<size_t, int> scoreCandidates(StringRef Filename) const { + // Decompose Filename into the parts we care about. + // /some/path/complicated/project/Interesting.h + // [-prefix--][---dir---] [-dir-] [--stem---] + StringRef Stem = sys::path::stem(Filename); + llvm::SmallVector<StringRef, DirectorySegmentsQueried> Dirs; + llvm::StringRef Prefix; + auto Dir = ++sys::path::rbegin(Filename), + DirEnd = sys::path::rend(Filename); + for (int I = 0; I < DirectorySegmentsQueried && Dir != DirEnd; ++I, ++Dir) { + if (Dir->size() > ShortDirectorySegment) + Dirs.push_back(*Dir); + Prefix = Filename.substr(0, Dir - DirEnd); + } + + // Now award points based on lookups into our various indexes. + DenseMap<size_t, int> Candidates; // Index -> score. + auto Award = [&](int Points, ArrayRef<SubstringAndIndex> Range) { + for (const auto &Entry : Range) + Candidates[Entry.second] += Points; + }; + // Award one point if the file's basename is a prefix of the candidate, + // and another if it's an exact match (so exact matches get two points). + Award(1, indexLookup</*Prefix=*/true>(Stem, Stems)); + Award(1, indexLookup</*Prefix=*/false>(Stem, Stems)); + // For each of the last few directories in the Filename, award a point + // if it's present in the candidate. + for (StringRef Dir : Dirs) + Award(1, indexLookup</*Prefix=*/false>(Dir, Components)); + // Award one more point if the whole rest of the path matches. + if (sys::path::root_directory(Prefix) != Prefix) + Award(1, indexLookup</*Prefix=*/true>(Prefix, Paths)); + return Candidates; + } + + // Pick a single winner from the set of scored candidates. + // Returns (index, score). + std::pair<size_t, int> pickWinner(const DenseMap<size_t, int> &Candidates, + StringRef Filename, + types::ID PreferredLanguage) const { + struct ScoredCandidate { + size_t Index; + bool Preferred; + int Points; + size_t PrefixLength; + }; + // Choose the best candidate by (preferred, points, prefix length, alpha). + ScoredCandidate Best = {size_t(-1), false, 0, 0}; + for (const auto &Candidate : Candidates) { + ScoredCandidate S; + S.Index = Candidate.first; + S.Preferred = PreferredLanguage == types::TY_INVALID || + PreferredLanguage == Types[S.Index]; + S.Points = Candidate.second; + if (!S.Preferred && Best.Preferred) + continue; + if (S.Preferred == Best.Preferred) { + if (S.Points < Best.Points) + continue; + if (S.Points == Best.Points) { + S.PrefixLength = matchingPrefix(Filename, Paths[S.Index].first); + if (S.PrefixLength < Best.PrefixLength) + continue; + // hidden heuristics should at least be deterministic! + if (S.PrefixLength == Best.PrefixLength) + if (S.Index > Best.Index) + continue; + } + } + // PrefixLength was only set above if actually needed for a tiebreak. + // But it definitely needs to be set to break ties in the future. + S.PrefixLength = matchingPrefix(Filename, Paths[S.Index].first); + Best = S; + } + // Edge case: no candidate got any points. + // We ignore PreferredLanguage at this point (not ideal). + if (Best.Index == size_t(-1)) + return {longestMatch(Filename, Paths).second, 0}; + return {Best.Index, Best.Points}; + } + + // Returns the range within a sorted index that compares equal to Key. + // If Prefix is true, it's instead the range starting with Key. + template <bool Prefix> + ArrayRef<SubstringAndIndex> + indexLookup(StringRef Key, ArrayRef<SubstringAndIndex> Idx) const { + // Use pointers as iteratiors to ease conversion of result to ArrayRef. + auto Range = std::equal_range(Idx.data(), Idx.data() + Idx.size(), Key, + Less<Prefix>()); + return {Range.first, Range.second}; + } + + // Performs a point lookup into a nonempty index, returning a longest match. + SubstringAndIndex longestMatch(StringRef Key, + ArrayRef<SubstringAndIndex> Idx) const { + assert(!Idx.empty()); + // Longest substring match will be adjacent to a direct lookup. + auto It = + std::lower_bound(Idx.begin(), Idx.end(), SubstringAndIndex{Key, 0}); + if (It == Idx.begin()) + return *It; + if (It == Idx.end()) + return *--It; + // Have to choose between It and It-1 + size_t Prefix = matchingPrefix(Key, It->first); + size_t PrevPrefix = matchingPrefix(Key, (It - 1)->first); + return Prefix > PrevPrefix ? *It : *--It; + } + + // Original paths, everything else is in lowercase. + std::vector<std::string> OriginalPaths; + BumpPtrAllocator Arena; + StringSaver Strings; + // Indexes of candidates by certain substrings. + // String is lowercase and sorted, index points into OriginalPaths. + std::vector<SubstringAndIndex> Paths; // Full path. + // Lang types obtained by guessing on the corresponding path. I-th element is + // a type for the I-th path. + std::vector<types::ID> Types; + std::vector<SubstringAndIndex> Stems; // Basename, without extension. + std::vector<SubstringAndIndex> Components; // Last path components. +}; + +// The actual CompilationDatabase wrapper delegates to its inner database. +// If no match, looks up a proxy file in FileIndex and transfers its +// command to the requested file. +class InterpolatingCompilationDatabase : public CompilationDatabase { +public: + InterpolatingCompilationDatabase(std::unique_ptr<CompilationDatabase> Inner) + : Inner(std::move(Inner)), Index(this->Inner->getAllFiles()) {} + + std::vector<CompileCommand> + getCompileCommands(StringRef Filename) const override { + auto Known = Inner->getCompileCommands(Filename); + if (Index.empty() || !Known.empty()) + return Known; + bool TypeCertain; + auto Lang = guessType(Filename, &TypeCertain); + if (!TypeCertain) + Lang = types::TY_INVALID; + auto ProxyCommands = + Inner->getCompileCommands(Index.chooseProxy(Filename, foldType(Lang))); + if (ProxyCommands.empty()) + return {}; + return {TransferableCommand(ProxyCommands[0]).transferTo(Filename)}; + } + + std::vector<std::string> getAllFiles() const override { + return Inner->getAllFiles(); + } + + std::vector<CompileCommand> getAllCompileCommands() const override { + return Inner->getAllCompileCommands(); + } + +private: + std::unique_ptr<CompilationDatabase> Inner; + FileIndex Index; +}; + +} // namespace + +std::unique_ptr<CompilationDatabase> +inferMissingCompileCommands(std::unique_ptr<CompilationDatabase> Inner) { + return llvm::make_unique<InterpolatingCompilationDatabase>(std::move(Inner)); +} + +} // namespace tooling +} // namespace clang diff --git a/contrib/llvm/tools/clang/lib/Tooling/JSONCompilationDatabase.cpp b/contrib/llvm/tools/clang/lib/Tooling/JSONCompilationDatabase.cpp new file mode 100644 index 000000000000..b0feaa229c11 --- /dev/null +++ b/contrib/llvm/tools/clang/lib/Tooling/JSONCompilationDatabase.cpp @@ -0,0 +1,384 @@ +//===- JSONCompilationDatabase.cpp ----------------------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// This file contains the implementation of the JSONCompilationDatabase. +// +//===----------------------------------------------------------------------===// + +#include "clang/Tooling/JSONCompilationDatabase.h" +#include "clang/Basic/LLVM.h" +#include "clang/Tooling/CompilationDatabase.h" +#include "clang/Tooling/CompilationDatabasePluginRegistry.h" +#include "llvm/ADT/Optional.h" +#include "llvm/ADT/SmallString.h" +#include "llvm/ADT/SmallVector.h" +#include "llvm/ADT/StringRef.h" +#include "llvm/ADT/Triple.h" +#include "llvm/Support/Allocator.h" +#include "llvm/Support/Casting.h" +#include "llvm/Support/CommandLine.h" +#include "llvm/Support/ErrorOr.h" +#include "llvm/Support/Host.h" +#include "llvm/Support/MemoryBuffer.h" +#include "llvm/Support/Path.h" +#include "llvm/Support/StringSaver.h" +#include "llvm/Support/YAMLParser.h" +#include "llvm/Support/raw_ostream.h" +#include <cassert> +#include <memory> +#include <string> +#include <system_error> +#include <tuple> +#include <utility> +#include <vector> + +using namespace clang; +using namespace tooling; + +namespace { + +/// A parser for escaped strings of command line arguments. +/// +/// Assumes \-escaping for quoted arguments (see the documentation of +/// unescapeCommandLine(...)). +class CommandLineArgumentParser { + public: + CommandLineArgumentParser(StringRef CommandLine) + : Input(CommandLine), Position(Input.begin()-1) {} + + std::vector<std::string> parse() { + bool HasMoreInput = true; + while (HasMoreInput && nextNonWhitespace()) { + std::string Argument; + HasMoreInput = parseStringInto(Argument); + CommandLine.push_back(Argument); + } + return CommandLine; + } + + private: + // All private methods return true if there is more input available. + + bool parseStringInto(std::string &String) { + do { + if (*Position == '"') { + if (!parseDoubleQuotedStringInto(String)) return false; + } else if (*Position == '\'') { + if (!parseSingleQuotedStringInto(String)) return false; + } else { + if (!parseFreeStringInto(String)) return false; + } + } while (*Position != ' '); + return true; + } + + bool parseDoubleQuotedStringInto(std::string &String) { + if (!next()) return false; + while (*Position != '"') { + if (!skipEscapeCharacter()) return false; + String.push_back(*Position); + if (!next()) return false; + } + return next(); + } + + bool parseSingleQuotedStringInto(std::string &String) { + if (!next()) return false; + while (*Position != '\'') { + String.push_back(*Position); + if (!next()) return false; + } + return next(); + } + + bool parseFreeStringInto(std::string &String) { + do { + if (!skipEscapeCharacter()) return false; + String.push_back(*Position); + if (!next()) return false; + } while (*Position != ' ' && *Position != '"' && *Position != '\''); + return true; + } + + bool skipEscapeCharacter() { + if (*Position == '\\') { + return next(); + } + return true; + } + + bool nextNonWhitespace() { + do { + if (!next()) return false; + } while (*Position == ' '); + return true; + } + + bool next() { + ++Position; + return Position != Input.end(); + } + + const StringRef Input; + StringRef::iterator Position; + std::vector<std::string> CommandLine; +}; + +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(); +} + +// This plugin locates a nearby compile_command.json file, and also infers +// compile commands for files not present in the database. +class JSONCompilationDatabasePlugin : public CompilationDatabasePlugin { + std::unique_ptr<CompilationDatabase> + loadFromDirectory(StringRef Directory, std::string &ErrorMessage) override { + SmallString<1024> JSONDatabasePath(Directory); + llvm::sys::path::append(JSONDatabasePath, "compile_commands.json"); + auto Base = JSONCompilationDatabase::loadFromFile( + JSONDatabasePath, ErrorMessage, JSONCommandLineSyntax::AutoDetect); + return Base ? inferMissingCompileCommands(std::move(Base)) : nullptr; + } +}; + +} // namespace + +// Register the JSONCompilationDatabasePlugin with the +// CompilationDatabasePluginRegistry using this statically initialized variable. +static CompilationDatabasePluginRegistry::Add<JSONCompilationDatabasePlugin> +X("json-compilation-database", "Reads JSON formatted compilation databases"); + +namespace clang { +namespace tooling { + +// This anchor is used to force the linker to link in the generated object file +// and thus register the JSONCompilationDatabasePlugin. +volatile int JSONAnchorSource = 0; + +} // namespace tooling +} // namespace clang + +std::unique_ptr<JSONCompilationDatabase> +JSONCompilationDatabase::loadFromFile(StringRef FilePath, + std::string &ErrorMessage, + JSONCommandLineSyntax Syntax) { + llvm::ErrorOr<std::unique_ptr<llvm::MemoryBuffer>> DatabaseBuffer = + llvm::MemoryBuffer::getFile(FilePath); + if (std::error_code Result = DatabaseBuffer.getError()) { + ErrorMessage = "Error while opening JSON database: " + Result.message(); + return nullptr; + } + std::unique_ptr<JSONCompilationDatabase> Database( + new JSONCompilationDatabase(std::move(*DatabaseBuffer), Syntax)); + if (!Database->parse(ErrorMessage)) + return nullptr; + return Database; +} + +std::unique_ptr<JSONCompilationDatabase> +JSONCompilationDatabase::loadFromBuffer(StringRef DatabaseString, + 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), Syntax)); + if (!Database->parse(ErrorMessage)) + return nullptr; + return Database; +} + +std::vector<CompileCommand> +JSONCompilationDatabase::getCompileCommands(StringRef FilePath) const { + SmallString<128> NativeFilePath; + llvm::sys::path::native(FilePath, NativeFilePath); + + std::string Error; + llvm::raw_string_ostream ES(Error); + StringRef Match = MatchTrie.findEquivalent(NativeFilePath, ES); + if (Match.empty()) + return {}; + const auto CommandsRefI = IndexByFile.find(Match); + if (CommandsRefI == IndexByFile.end()) + return {}; + std::vector<CompileCommand> Commands; + getCommands(CommandsRefI->getValue(), Commands); + return Commands; +} + +std::vector<std::string> +JSONCompilationDatabase::getAllFiles() const { + std::vector<std::string> Result; + for (const auto &CommandRef : IndexByFile) + Result.push_back(CommandRef.first().str()); + return Result; +} + +std::vector<CompileCommand> +JSONCompilationDatabase::getAllCompileCommands() const { + std::vector<CompileCommand> Commands; + getCommands(AllCommands, Commands); + return Commands; +} + +static std::vector<std::string> +nodeToCommandLine(JSONCommandLineSyntax Syntax, + const std::vector<llvm::yaml::ScalarNode *> &Nodes) { + SmallString<1024> Storage; + if (Nodes.size() == 1) + return unescapeCommandLine(Syntax, Nodes[0]->getValue(Storage)); + std::vector<std::string> Arguments; + for (const auto *Node : Nodes) + Arguments.push_back(Node->getValue(Storage)); + return Arguments; +} + +void JSONCompilationDatabase::getCommands( + ArrayRef<CompileCommandRef> CommandsRef, + std::vector<CompileCommand> &Commands) const { + for (const auto &CommandRef : CommandsRef) { + SmallString<8> DirectoryStorage; + SmallString<32> FilenameStorage; + SmallString<32> OutputStorage; + auto Output = std::get<3>(CommandRef); + Commands.emplace_back( + std::get<0>(CommandRef)->getValue(DirectoryStorage), + std::get<1>(CommandRef)->getValue(FilenameStorage), + nodeToCommandLine(Syntax, std::get<2>(CommandRef)), + Output ? Output->getValue(OutputStorage) : ""); + } +} + +bool JSONCompilationDatabase::parse(std::string &ErrorMessage) { + llvm::yaml::document_iterator I = YAMLStream.begin(); + if (I == YAMLStream.end()) { + ErrorMessage = "Error while parsing YAML."; + return false; + } + llvm::yaml::Node *Root = I->getRoot(); + if (!Root) { + ErrorMessage = "Error while parsing YAML."; + return false; + } + auto *Array = dyn_cast<llvm::yaml::SequenceNode>(Root); + if (!Array) { + ErrorMessage = "Expected array."; + return false; + } + for (auto &NextObject : *Array) { + auto *Object = dyn_cast<llvm::yaml::MappingNode>(&NextObject); + if (!Object) { + ErrorMessage = "Expected object."; + return false; + } + 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) { + auto *KeyString = dyn_cast<llvm::yaml::ScalarNode>(NextKeyValue.getKey()); + if (!KeyString) { + ErrorMessage = "Expected strings as key."; + return false; + } + SmallString<10> KeyStorage; + StringRef KeyValue = KeyString->getValue(KeyStorage); + llvm::yaml::Node *Value = NextKeyValue.getValue(); + if (!Value) { + ErrorMessage = "Expected value."; + return false; + } + auto *ValueString = dyn_cast<llvm::yaml::ScalarNode>(Value); + auto *SequenceString = dyn_cast<llvm::yaml::SequenceNode>(Value); + if (KeyValue == "arguments" && !SequenceString) { + ErrorMessage = "Expected sequence as value."; + return false; + } else if (KeyValue != "arguments" && !ValueString) { + ErrorMessage = "Expected string as value."; + return false; + } + if (KeyValue == "directory") { + Directory = ValueString; + } else if (KeyValue == "arguments") { + Command = std::vector<llvm::yaml::ScalarNode *>(); + for (auto &Argument : *SequenceString) { + auto *Scalar = dyn_cast<llvm::yaml::ScalarNode>(&Argument); + if (!Scalar) { + ErrorMessage = "Only strings are allowed in 'arguments'."; + return false; + } + Command->push_back(Scalar); + } + } else if (KeyValue == "command") { + if (!Command) + 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(); + return false; + } + } + if (!File) { + ErrorMessage = "Missing key: \"file\"."; + return false; + } + if (!Command) { + ErrorMessage = "Missing key: \"command\" or \"arguments\"."; + return false; + } + if (!Directory) { + ErrorMessage = "Missing key: \"directory\"."; + return false; + } + SmallString<8> FileStorage; + StringRef FileName = File->getValue(FileStorage); + SmallString<128> NativeFilePath; + if (llvm::sys::path::is_relative(FileName)) { + SmallString<8> DirectoryStorage; + SmallString<128> AbsolutePath( + Directory->getValue(DirectoryStorage)); + llvm::sys::path::append(AbsolutePath, FileName); + llvm::sys::path::native(AbsolutePath, NativeFilePath); + } else { + llvm::sys::path::native(FileName, NativeFilePath); + } + auto Cmd = CompileCommandRef(Directory, File, *Command, Output); + IndexByFile[NativeFilePath].push_back(Cmd); + AllCommands.push_back(Cmd); + MatchTrie.insert(NativeFilePath); + } + return true; +} diff --git a/contrib/llvm/tools/clang/lib/Tooling/Refactoring.cpp b/contrib/llvm/tools/clang/lib/Tooling/Refactoring.cpp new file mode 100644 index 000000000000..db34c952d794 --- /dev/null +++ b/contrib/llvm/tools/clang/lib/Tooling/Refactoring.cpp @@ -0,0 +1,104 @@ +//===--- Refactoring.cpp - Framework for clang refactoring tools ----------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// Implements tools to support refactorings. +// +//===----------------------------------------------------------------------===// + +#include "clang/Tooling/Refactoring.h" +#include "clang/Basic/DiagnosticOptions.h" +#include "clang/Basic/FileManager.h" +#include "clang/Basic/SourceManager.h" +#include "clang/Format/Format.h" +#include "clang/Frontend/TextDiagnosticPrinter.h" +#include "clang/Lex/Lexer.h" +#include "clang/Rewrite/Core/Rewriter.h" +#include "llvm/Support/Path.h" +#include "llvm/Support/raw_os_ostream.h" + +namespace clang { +namespace tooling { + +RefactoringTool::RefactoringTool( + const CompilationDatabase &Compilations, ArrayRef<std::string> SourcePaths, + std::shared_ptr<PCHContainerOperations> PCHContainerOps) + : ClangTool(Compilations, SourcePaths, std::move(PCHContainerOps)) {} + +std::map<std::string, Replacements> &RefactoringTool::getReplacements() { + return FileToReplaces; +} + +int RefactoringTool::runAndSave(FrontendActionFactory *ActionFactory) { + if (int Result = run(ActionFactory)) { + return Result; + } + + LangOptions DefaultLangOptions; + IntrusiveRefCntPtr<DiagnosticOptions> DiagOpts = new DiagnosticOptions(); + TextDiagnosticPrinter DiagnosticPrinter(llvm::errs(), &*DiagOpts); + DiagnosticsEngine Diagnostics( + IntrusiveRefCntPtr<DiagnosticIDs>(new DiagnosticIDs()), + &*DiagOpts, &DiagnosticPrinter, false); + SourceManager Sources(Diagnostics, getFiles()); + Rewriter Rewrite(Sources, DefaultLangOptions); + + if (!applyAllReplacements(Rewrite)) { + llvm::errs() << "Skipped some replacements.\n"; + } + + return saveRewrittenFiles(Rewrite); +} + +bool RefactoringTool::applyAllReplacements(Rewriter &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 std::map<std::string, Replacements> &FileToReplaces, + Rewriter &Rewrite, StringRef Style) { + SourceManager &SM = Rewrite.getSourceMgr(); + FileManager &Files = SM.getFileManager(); + + bool Result = true; + for (const auto &FileAndReplaces : groupReplacementsByFile( + Rewrite.getSourceMgr().getFileManager(), FileToReplaces)) { + const std::string &FilePath = FileAndReplaces.first; + auto &CurReplaces = FileAndReplaces.second; + + const FileEntry *Entry = Files.getFile(FilePath); + FileID ID = SM.getOrCreateFileID(Entry, SrcMgr::C_User); + StringRef Code = SM.getBufferData(ID); + + auto CurStyle = format::getStyle(Style, FilePath, "LLVM"); + if (!CurStyle) { + llvm::errs() << llvm::toString(CurStyle.takeError()) << "\n"; + return false; + } + + auto NewReplacements = + format::formatReplacements(Code, CurReplaces, *CurStyle); + if (!NewReplacements) { + llvm::errs() << llvm::toString(NewReplacements.takeError()) << "\n"; + return false; + } + Result = applyAllReplacements(*NewReplacements, Rewrite) && Result; + } + return Result; +} + +} // end namespace tooling +} // end namespace clang diff --git a/contrib/llvm/tools/clang/lib/Tooling/Refactoring/ASTSelection.cpp b/contrib/llvm/tools/clang/lib/Tooling/Refactoring/ASTSelection.cpp new file mode 100644 index 000000000000..b8f996d8218c --- /dev/null +++ b/contrib/llvm/tools/clang/lib/Tooling/Refactoring/ASTSelection.cpp @@ -0,0 +1,451 @@ +//===--- ASTSelection.cpp - Clang refactoring library ---------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "clang/Tooling/Refactoring/ASTSelection.h" +#include "clang/AST/LexicallyOrderedRecursiveASTVisitor.h" +#include "clang/Lex/Lexer.h" +#include "llvm/Support/SaveAndRestore.h" + +using namespace clang; +using namespace tooling; +using ast_type_traits::DynTypedNode; + +namespace { + +CharSourceRange getLexicalDeclRange(Decl *D, const SourceManager &SM, + const LangOptions &LangOpts) { + if (!isa<ObjCImplDecl>(D)) + return CharSourceRange::getTokenRange(D->getSourceRange()); + // Objective-C implementation declarations end at the '@' instead of the 'end' + // keyword. Use the lexer to find the location right after 'end'. + SourceRange R = D->getSourceRange(); + SourceLocation LocAfterEnd = Lexer::findLocationAfterToken( + R.getEnd(), tok::raw_identifier, SM, LangOpts, + /*SkipTrailingWhitespaceAndNewLine=*/false); + return LocAfterEnd.isValid() + ? CharSourceRange::getCharRange(R.getBegin(), LocAfterEnd) + : CharSourceRange::getTokenRange(R); +} + +/// Constructs the tree of selected AST nodes that either contain the location +/// of the cursor or overlap with the selection range. +class ASTSelectionFinder + : public LexicallyOrderedRecursiveASTVisitor<ASTSelectionFinder> { +public: + ASTSelectionFinder(SourceRange Selection, FileID TargetFile, + const ASTContext &Context) + : LexicallyOrderedRecursiveASTVisitor(Context.getSourceManager()), + SelectionBegin(Selection.getBegin()), + SelectionEnd(Selection.getBegin() == Selection.getEnd() + ? SourceLocation() + : Selection.getEnd()), + TargetFile(TargetFile), Context(Context) { + // The TU decl is the root of the selected node tree. + SelectionStack.push_back( + SelectedASTNode(DynTypedNode::create(*Context.getTranslationUnitDecl()), + SourceSelectionKind::None)); + } + + Optional<SelectedASTNode> getSelectedASTNode() { + assert(SelectionStack.size() == 1 && "stack was not popped"); + SelectedASTNode Result = std::move(SelectionStack.back()); + SelectionStack.pop_back(); + if (Result.Children.empty()) + return None; + return std::move(Result); + } + + bool TraversePseudoObjectExpr(PseudoObjectExpr *E) { + // Avoid traversing the semantic expressions. They should be handled by + // looking through the appropriate opaque expressions in order to build + // a meaningful selection tree. + llvm::SaveAndRestore<bool> LookThrough(LookThroughOpaqueValueExprs, true); + return TraverseStmt(E->getSyntacticForm()); + } + + bool TraverseOpaqueValueExpr(OpaqueValueExpr *E) { + if (!LookThroughOpaqueValueExprs) + return true; + llvm::SaveAndRestore<bool> LookThrough(LookThroughOpaqueValueExprs, false); + return TraverseStmt(E->getSourceExpr()); + } + + bool TraverseDecl(Decl *D) { + if (isa<TranslationUnitDecl>(D)) + return LexicallyOrderedRecursiveASTVisitor::TraverseDecl(D); + if (D->isImplicit()) + return true; + + // Check if this declaration is written in the file of interest. + const SourceRange DeclRange = D->getSourceRange(); + const SourceManager &SM = Context.getSourceManager(); + SourceLocation FileLoc; + if (DeclRange.getBegin().isMacroID() && !DeclRange.getEnd().isMacroID()) + FileLoc = DeclRange.getEnd(); + else + FileLoc = SM.getSpellingLoc(DeclRange.getBegin()); + if (SM.getFileID(FileLoc) != TargetFile) + return true; + + SourceSelectionKind SelectionKind = + selectionKindFor(getLexicalDeclRange(D, SM, Context.getLangOpts())); + SelectionStack.push_back( + SelectedASTNode(DynTypedNode::create(*D), SelectionKind)); + LexicallyOrderedRecursiveASTVisitor::TraverseDecl(D); + popAndAddToSelectionIfSelected(SelectionKind); + + if (DeclRange.getEnd().isValid() && + SM.isBeforeInTranslationUnit(SelectionEnd.isValid() ? SelectionEnd + : SelectionBegin, + DeclRange.getEnd())) { + // Stop early when we've reached a declaration after the selection. + return false; + } + return true; + } + + bool TraverseStmt(Stmt *S) { + if (!S) + return true; + if (auto *Opaque = dyn_cast<OpaqueValueExpr>(S)) + return TraverseOpaqueValueExpr(Opaque); + // Avoid selecting implicit 'this' expressions. + if (auto *TE = dyn_cast<CXXThisExpr>(S)) { + if (TE->isImplicit()) + return true; + } + // FIXME (Alex Lorenz): Improve handling for macro locations. + SourceSelectionKind SelectionKind = + selectionKindFor(CharSourceRange::getTokenRange(S->getSourceRange())); + SelectionStack.push_back( + SelectedASTNode(DynTypedNode::create(*S), SelectionKind)); + LexicallyOrderedRecursiveASTVisitor::TraverseStmt(S); + popAndAddToSelectionIfSelected(SelectionKind); + return true; + } + +private: + void popAndAddToSelectionIfSelected(SourceSelectionKind SelectionKind) { + SelectedASTNode Node = std::move(SelectionStack.back()); + SelectionStack.pop_back(); + if (SelectionKind != SourceSelectionKind::None || !Node.Children.empty()) + SelectionStack.back().Children.push_back(std::move(Node)); + } + + SourceSelectionKind selectionKindFor(CharSourceRange Range) { + SourceLocation End = Range.getEnd(); + const SourceManager &SM = Context.getSourceManager(); + if (Range.isTokenRange()) + End = Lexer::getLocForEndOfToken(End, 0, SM, Context.getLangOpts()); + if (!SourceLocation::isPairOfFileLocations(Range.getBegin(), End)) + return SourceSelectionKind::None; + if (!SelectionEnd.isValid()) { + // Do a quick check when the selection is of length 0. + if (SM.isPointWithin(SelectionBegin, Range.getBegin(), End)) + return SourceSelectionKind::ContainsSelection; + return SourceSelectionKind::None; + } + bool HasStart = SM.isPointWithin(SelectionBegin, Range.getBegin(), End); + bool HasEnd = SM.isPointWithin(SelectionEnd, Range.getBegin(), End); + if (HasStart && HasEnd) + return SourceSelectionKind::ContainsSelection; + if (SM.isPointWithin(Range.getBegin(), SelectionBegin, SelectionEnd) && + SM.isPointWithin(End, SelectionBegin, SelectionEnd)) + return SourceSelectionKind::InsideSelection; + // Ensure there's at least some overlap with the 'start'/'end' selection + // types. + if (HasStart && SelectionBegin != End) + return SourceSelectionKind::ContainsSelectionStart; + if (HasEnd && SelectionEnd != Range.getBegin()) + return SourceSelectionKind::ContainsSelectionEnd; + + return SourceSelectionKind::None; + } + + const SourceLocation SelectionBegin, SelectionEnd; + FileID TargetFile; + const ASTContext &Context; + std::vector<SelectedASTNode> SelectionStack; + /// Controls whether we can traverse through the OpaqueValueExpr. This is + /// typically enabled during the traversal of syntactic form for + /// PseudoObjectExprs. + bool LookThroughOpaqueValueExprs = false; +}; + +} // end anonymous namespace + +Optional<SelectedASTNode> +clang::tooling::findSelectedASTNodes(const ASTContext &Context, + SourceRange SelectionRange) { + assert(SelectionRange.isValid() && + SourceLocation::isPairOfFileLocations(SelectionRange.getBegin(), + SelectionRange.getEnd()) && + "Expected a file range"); + FileID TargetFile = + Context.getSourceManager().getFileID(SelectionRange.getBegin()); + assert(Context.getSourceManager().getFileID(SelectionRange.getEnd()) == + TargetFile && + "selection range must span one file"); + + ASTSelectionFinder Visitor(SelectionRange, TargetFile, Context); + Visitor.TraverseDecl(Context.getTranslationUnitDecl()); + return Visitor.getSelectedASTNode(); +} + +static const char *selectionKindToString(SourceSelectionKind Kind) { + switch (Kind) { + case SourceSelectionKind::None: + return "none"; + case SourceSelectionKind::ContainsSelection: + return "contains-selection"; + case SourceSelectionKind::ContainsSelectionStart: + return "contains-selection-start"; + case SourceSelectionKind::ContainsSelectionEnd: + return "contains-selection-end"; + case SourceSelectionKind::InsideSelection: + return "inside"; + } + llvm_unreachable("invalid selection kind"); +} + +static void dump(const SelectedASTNode &Node, llvm::raw_ostream &OS, + unsigned Indent = 0) { + OS.indent(Indent * 2); + if (const Decl *D = Node.Node.get<Decl>()) { + OS << D->getDeclKindName() << "Decl"; + if (const auto *ND = dyn_cast<NamedDecl>(D)) + OS << " \"" << ND->getNameAsString() << '"'; + } else if (const Stmt *S = Node.Node.get<Stmt>()) { + OS << S->getStmtClassName(); + } + OS << ' ' << selectionKindToString(Node.SelectionKind) << "\n"; + for (const auto &Child : Node.Children) + dump(Child, OS, Indent + 1); +} + +void SelectedASTNode::dump(llvm::raw_ostream &OS) const { ::dump(*this, OS); } + +/// Returns true if the given node has any direct children with the following +/// selection kind. +/// +/// Note: The direct children also include children of direct children with the +/// "None" selection kind. +static bool hasAnyDirectChildrenWithKind(const SelectedASTNode &Node, + SourceSelectionKind Kind) { + assert(Kind != SourceSelectionKind::None && "invalid predicate!"); + for (const auto &Child : Node.Children) { + if (Child.SelectionKind == Kind) + return true; + if (Child.SelectionKind == SourceSelectionKind::None) + return hasAnyDirectChildrenWithKind(Child, Kind); + } + return false; +} + +namespace { +struct SelectedNodeWithParents { + SelectedASTNode::ReferenceType Node; + llvm::SmallVector<SelectedASTNode::ReferenceType, 8> Parents; + + /// Canonicalizes the given selection by selecting different related AST nodes + /// when it makes sense to do so. + void canonicalize(); +}; + +enum SelectionCanonicalizationAction { KeepSelection, SelectParent }; + +/// Returns the canonicalization action which should be applied to the +/// selected statement. +SelectionCanonicalizationAction +getSelectionCanonizalizationAction(const Stmt *S, const Stmt *Parent) { + // Select the parent expression when: + // - The string literal in ObjC string literal is selected, e.g.: + // @"test" becomes @"test" + // ~~~~~~ ~~~~~~~ + if (isa<StringLiteral>(S) && isa<ObjCStringLiteral>(Parent)) + return SelectParent; + // The entire call should be selected when just the member expression + // that refers to the method or the decl ref that refers to the function + // is selected. + // f.call(args) becomes f.call(args) + // ~~~~ ~~~~~~~~~~~~ + // func(args) becomes func(args) + // ~~~~ ~~~~~~~~~~ + else if (const auto *CE = dyn_cast<CallExpr>(Parent)) { + if ((isa<MemberExpr>(S) || isa<DeclRefExpr>(S)) && + CE->getCallee()->IgnoreImpCasts() == S) + return SelectParent; + } + // FIXME: Syntactic form -> Entire pseudo-object expr. + return KeepSelection; +} + +} // end anonymous namespace + +void SelectedNodeWithParents::canonicalize() { + const Stmt *S = Node.get().Node.get<Stmt>(); + assert(S && "non statement selection!"); + const Stmt *Parent = Parents[Parents.size() - 1].get().Node.get<Stmt>(); + if (!Parent) + return; + + // Look through the implicit casts in the parents. + unsigned ParentIndex = 1; + for (; (ParentIndex + 1) <= Parents.size() && isa<ImplicitCastExpr>(Parent); + ++ParentIndex) { + const Stmt *NewParent = + Parents[Parents.size() - ParentIndex - 1].get().Node.get<Stmt>(); + if (!NewParent) + break; + Parent = NewParent; + } + + switch (getSelectionCanonizalizationAction(S, Parent)) { + case SelectParent: + Node = Parents[Parents.size() - ParentIndex]; + for (; ParentIndex != 0; --ParentIndex) + Parents.pop_back(); + break; + case KeepSelection: + break; + } +} + +/// Finds the set of bottom-most selected AST nodes that are in the selection +/// tree with the specified selection kind. +/// +/// For example, given the following selection tree: +/// +/// FunctionDecl "f" contains-selection +/// CompoundStmt contains-selection [#1] +/// CallExpr inside +/// ImplicitCastExpr inside +/// DeclRefExpr inside +/// IntegerLiteral inside +/// IntegerLiteral inside +/// FunctionDecl "f2" contains-selection +/// CompoundStmt contains-selection [#2] +/// CallExpr inside +/// ImplicitCastExpr inside +/// DeclRefExpr inside +/// IntegerLiteral inside +/// IntegerLiteral inside +/// +/// This function will find references to nodes #1 and #2 when searching for the +/// \c ContainsSelection kind. +static void findDeepestWithKind( + const SelectedASTNode &ASTSelection, + llvm::SmallVectorImpl<SelectedNodeWithParents> &MatchingNodes, + SourceSelectionKind Kind, + llvm::SmallVectorImpl<SelectedASTNode::ReferenceType> &ParentStack) { + if (ASTSelection.Node.get<DeclStmt>()) { + // Select the entire decl stmt when any of its child declarations is the + // bottom-most. + for (const auto &Child : ASTSelection.Children) { + if (!hasAnyDirectChildrenWithKind(Child, Kind)) { + MatchingNodes.push_back(SelectedNodeWithParents{ + std::cref(ASTSelection), {ParentStack.begin(), ParentStack.end()}}); + return; + } + } + } else { + if (!hasAnyDirectChildrenWithKind(ASTSelection, Kind)) { + // This node is the bottom-most. + MatchingNodes.push_back(SelectedNodeWithParents{ + std::cref(ASTSelection), {ParentStack.begin(), ParentStack.end()}}); + return; + } + } + // Search in the children. + ParentStack.push_back(std::cref(ASTSelection)); + for (const auto &Child : ASTSelection.Children) + findDeepestWithKind(Child, MatchingNodes, Kind, ParentStack); + ParentStack.pop_back(); +} + +static void findDeepestWithKind( + const SelectedASTNode &ASTSelection, + llvm::SmallVectorImpl<SelectedNodeWithParents> &MatchingNodes, + SourceSelectionKind Kind) { + llvm::SmallVector<SelectedASTNode::ReferenceType, 16> ParentStack; + findDeepestWithKind(ASTSelection, MatchingNodes, Kind, ParentStack); +} + +Optional<CodeRangeASTSelection> +CodeRangeASTSelection::create(SourceRange SelectionRange, + const SelectedASTNode &ASTSelection) { + // Code range is selected when the selection range is not empty. + if (SelectionRange.getBegin() == SelectionRange.getEnd()) + return None; + llvm::SmallVector<SelectedNodeWithParents, 4> ContainSelection; + findDeepestWithKind(ASTSelection, ContainSelection, + SourceSelectionKind::ContainsSelection); + // We are looking for a selection in one body of code, so let's focus on + // one matching result. + if (ContainSelection.size() != 1) + return None; + SelectedNodeWithParents &Selected = ContainSelection[0]; + if (!Selected.Node.get().Node.get<Stmt>()) + return None; + const Stmt *CodeRangeStmt = Selected.Node.get().Node.get<Stmt>(); + if (!isa<CompoundStmt>(CodeRangeStmt)) { + Selected.canonicalize(); + return CodeRangeASTSelection(Selected.Node, Selected.Parents, + /*AreChildrenSelected=*/false); + } + // FIXME (Alex L): First selected SwitchCase means that first case statement. + // is selected actually + // (See https://github.com/apple/swift-clang & CompoundStmtRange). + + // FIXME (Alex L): Tweak selection rules for compound statements, see: + // https://github.com/apple/swift-clang/blob/swift-4.1-branch/lib/Tooling/ + // Refactor/ASTSlice.cpp#L513 + // The user selected multiple statements in a compound statement. + Selected.Parents.push_back(Selected.Node); + return CodeRangeASTSelection(Selected.Node, Selected.Parents, + /*AreChildrenSelected=*/true); +} + +static bool isFunctionLikeDeclaration(const Decl *D) { + // FIXME (Alex L): Test for BlockDecl. + return isa<FunctionDecl>(D) || isa<ObjCMethodDecl>(D); +} + +bool CodeRangeASTSelection::isInFunctionLikeBodyOfCode() const { + bool IsPrevCompound = false; + // Scan through the parents (bottom-to-top) and check if the selection is + // contained in a compound statement that's a body of a function/method + // declaration. + for (const auto &Parent : llvm::reverse(Parents)) { + const DynTypedNode &Node = Parent.get().Node; + if (const auto *D = Node.get<Decl>()) { + if (isFunctionLikeDeclaration(D)) + return IsPrevCompound; + // Stop the search at any type declaration to avoid returning true for + // expressions in type declarations in functions, like: + // function foo() { struct X { + // int m = /*selection:*/ 1 + 2 /*selection end*/; }; }; + if (isa<TypeDecl>(D)) + return false; + } + IsPrevCompound = Node.get<CompoundStmt>() != nullptr; + } + return false; +} + +const Decl *CodeRangeASTSelection::getFunctionLikeNearestParent() const { + for (const auto &Parent : llvm::reverse(Parents)) { + const DynTypedNode &Node = Parent.get().Node; + if (const auto *D = Node.get<Decl>()) { + if (isFunctionLikeDeclaration(D)) + return D; + } + } + return nullptr; +} diff --git a/contrib/llvm/tools/clang/lib/Tooling/Refactoring/ASTSelectionRequirements.cpp b/contrib/llvm/tools/clang/lib/Tooling/Refactoring/ASTSelectionRequirements.cpp new file mode 100644 index 000000000000..c0232c5da442 --- /dev/null +++ b/contrib/llvm/tools/clang/lib/Tooling/Refactoring/ASTSelectionRequirements.cpp @@ -0,0 +1,48 @@ +//===--- ASTSelectionRequirements.cpp - Clang refactoring library ---------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "clang/Tooling/Refactoring/RefactoringActionRuleRequirements.h" + +using namespace clang; +using namespace tooling; + +Expected<SelectedASTNode> +ASTSelectionRequirement::evaluate(RefactoringRuleContext &Context) const { + // FIXME: Memoize so that selection is evaluated only once. + Expected<SourceRange> Range = + SourceRangeSelectionRequirement::evaluate(Context); + if (!Range) + return Range.takeError(); + + Optional<SelectedASTNode> Selection = + findSelectedASTNodes(Context.getASTContext(), *Range); + if (!Selection) + return Context.createDiagnosticError( + Range->getBegin(), diag::err_refactor_selection_invalid_ast); + return std::move(*Selection); +} + +Expected<CodeRangeASTSelection> CodeRangeASTSelectionRequirement::evaluate( + RefactoringRuleContext &Context) const { + // FIXME: Memoize so that selection is evaluated only once. + Expected<SelectedASTNode> ASTSelection = + ASTSelectionRequirement::evaluate(Context); + if (!ASTSelection) + return ASTSelection.takeError(); + std::unique_ptr<SelectedASTNode> StoredSelection = + llvm::make_unique<SelectedASTNode>(std::move(*ASTSelection)); + Optional<CodeRangeASTSelection> CodeRange = CodeRangeASTSelection::create( + Context.getSelectionRange(), *StoredSelection); + if (!CodeRange) + return Context.createDiagnosticError( + Context.getSelectionRange().getBegin(), + diag::err_refactor_selection_invalid_ast); + Context.setASTSelection(std::move(StoredSelection)); + return std::move(*CodeRange); +} diff --git a/contrib/llvm/tools/clang/lib/Tooling/Refactoring/AtomicChange.cpp b/contrib/llvm/tools/clang/lib/Tooling/Refactoring/AtomicChange.cpp new file mode 100644 index 000000000000..e8b0fdbeb662 --- /dev/null +++ b/contrib/llvm/tools/clang/lib/Tooling/Refactoring/AtomicChange.cpp @@ -0,0 +1,365 @@ +//===--- AtomicChange.cpp - AtomicChange implementation -----------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "clang/Tooling/Refactoring/AtomicChange.h" +#include "clang/Tooling/ReplacementsYaml.h" +#include "llvm/Support/YAMLTraits.h" +#include <string> + +LLVM_YAML_IS_SEQUENCE_VECTOR(clang::tooling::AtomicChange) + +namespace { +/// Helper to (de)serialize an AtomicChange since we don't have direct +/// access to its data members. +/// Data members of a normalized AtomicChange can be directly mapped from/to +/// YAML string. +struct NormalizedAtomicChange { + NormalizedAtomicChange() = default; + + NormalizedAtomicChange(const llvm::yaml::IO &) {} + + // This converts AtomicChange's internal implementation of the replacements + // set to a vector of replacements. + NormalizedAtomicChange(const llvm::yaml::IO &, + const clang::tooling::AtomicChange &E) + : Key(E.getKey()), FilePath(E.getFilePath()), Error(E.getError()), + InsertedHeaders(E.getInsertedHeaders()), + RemovedHeaders(E.getRemovedHeaders()), + Replaces(E.getReplacements().begin(), E.getReplacements().end()) {} + + // This is not expected to be called but needed for template instantiation. + clang::tooling::AtomicChange denormalize(const llvm::yaml::IO &) { + llvm_unreachable("Do not convert YAML to AtomicChange directly with '>>'. " + "Use AtomicChange::convertFromYAML instead."); + } + std::string Key; + std::string FilePath; + std::string Error; + std::vector<std::string> InsertedHeaders; + std::vector<std::string> RemovedHeaders; + std::vector<clang::tooling::Replacement> Replaces; +}; +} // anonymous namespace + +namespace llvm { +namespace yaml { + +/// Specialized MappingTraits to describe how an AtomicChange is +/// (de)serialized. +template <> struct MappingTraits<NormalizedAtomicChange> { + static void mapping(IO &Io, NormalizedAtomicChange &Doc) { + Io.mapRequired("Key", Doc.Key); + Io.mapRequired("FilePath", Doc.FilePath); + Io.mapRequired("Error", Doc.Error); + Io.mapRequired("InsertedHeaders", Doc.InsertedHeaders); + Io.mapRequired("RemovedHeaders", Doc.RemovedHeaders); + Io.mapRequired("Replacements", Doc.Replaces); + } +}; + +/// Specialized MappingTraits to describe how an AtomicChange is +/// (de)serialized. +template <> struct MappingTraits<clang::tooling::AtomicChange> { + static void mapping(IO &Io, clang::tooling::AtomicChange &Doc) { + MappingNormalization<NormalizedAtomicChange, clang::tooling::AtomicChange> + Keys(Io, Doc); + Io.mapRequired("Key", Keys->Key); + Io.mapRequired("FilePath", Keys->FilePath); + Io.mapRequired("Error", Keys->Error); + Io.mapRequired("InsertedHeaders", Keys->InsertedHeaders); + Io.mapRequired("RemovedHeaders", Keys->RemovedHeaders); + Io.mapRequired("Replacements", Keys->Replaces); + } +}; + +} // end namespace yaml +} // end namespace llvm + +namespace clang { +namespace tooling { +namespace { + +// Returns true if there is any line that violates \p ColumnLimit in range +// [Start, End]. +bool violatesColumnLimit(llvm::StringRef Code, unsigned ColumnLimit, + unsigned Start, unsigned End) { + auto StartPos = Code.rfind('\n', Start); + StartPos = (StartPos == llvm::StringRef::npos) ? 0 : StartPos + 1; + + auto EndPos = Code.find("\n", End); + if (EndPos == llvm::StringRef::npos) + EndPos = Code.size(); + + llvm::SmallVector<llvm::StringRef, 8> Lines; + Code.substr(StartPos, EndPos - StartPos).split(Lines, '\n'); + for (llvm::StringRef Line : Lines) + if (Line.size() > ColumnLimit) + return true; + return false; +} + +std::vector<Range> +getRangesForFormating(llvm::StringRef Code, unsigned ColumnLimit, + ApplyChangesSpec::FormatOption Format, + const clang::tooling::Replacements &Replaces) { + // kNone suppresses formatting entirely. + if (Format == ApplyChangesSpec::kNone) + return {}; + std::vector<clang::tooling::Range> Ranges; + // This works assuming that replacements are ordered by offset. + // FIXME: use `getAffectedRanges()` to calculate when it does not include '\n' + // at the end of an insertion in affected ranges. + int Offset = 0; + for (const clang::tooling::Replacement &R : Replaces) { + int Start = R.getOffset() + Offset; + int End = Start + R.getReplacementText().size(); + if (!R.getReplacementText().empty() && + R.getReplacementText().back() == '\n' && R.getLength() == 0 && + R.getOffset() > 0 && R.getOffset() <= Code.size() && + Code[R.getOffset() - 1] == '\n') + // If we are inserting at the start of a line and the replacement ends in + // a newline, we don't need to format the subsequent line. + --End; + Offset += R.getReplacementText().size() - R.getLength(); + + if (Format == ApplyChangesSpec::kAll || + violatesColumnLimit(Code, ColumnLimit, Start, End)) + Ranges.emplace_back(Start, End - Start); + } + return Ranges; +} + +inline llvm::Error make_string_error(const llvm::Twine &Message) { + return llvm::make_error<llvm::StringError>(Message, + llvm::inconvertibleErrorCode()); +} + +// Creates replacements for inserting/deleting #include headers. +llvm::Expected<Replacements> +createReplacementsForHeaders(llvm::StringRef FilePath, llvm::StringRef Code, + llvm::ArrayRef<AtomicChange> Changes, + const format::FormatStyle &Style) { + // Create header insertion/deletion replacements to be cleaned up + // (i.e. converted to real insertion/deletion replacements). + Replacements HeaderReplacements; + for (const auto &Change : Changes) { + for (llvm::StringRef Header : Change.getInsertedHeaders()) { + std::string EscapedHeader = + Header.startswith("<") || Header.startswith("\"") + ? Header.str() + : ("\"" + Header + "\"").str(); + std::string ReplacementText = "#include " + EscapedHeader; + // Offset UINT_MAX and length 0 indicate that the replacement is a header + // insertion. + llvm::Error Err = HeaderReplacements.add( + tooling::Replacement(FilePath, UINT_MAX, 0, ReplacementText)); + if (Err) + return std::move(Err); + } + for (const std::string &Header : Change.getRemovedHeaders()) { + // Offset UINT_MAX and length 1 indicate that the replacement is a header + // deletion. + llvm::Error Err = + HeaderReplacements.add(Replacement(FilePath, UINT_MAX, 1, Header)); + if (Err) + return std::move(Err); + } + } + + // cleanupAroundReplacements() converts header insertions/deletions into + // actual replacements that add/remove headers at the right location. + return clang::format::cleanupAroundReplacements(Code, HeaderReplacements, + Style); +} + +// Combine replacements in all Changes as a `Replacements`. This ignores the +// file path in all replacements and replaces them with \p FilePath. +llvm::Expected<Replacements> +combineReplacementsInChanges(llvm::StringRef FilePath, + llvm::ArrayRef<AtomicChange> Changes) { + Replacements Replaces; + for (const auto &Change : Changes) + for (const auto &R : Change.getReplacements()) + if (auto Err = Replaces.add(Replacement( + FilePath, R.getOffset(), R.getLength(), R.getReplacementText()))) + return std::move(Err); + return Replaces; +} + +} // end namespace + +AtomicChange::AtomicChange(const SourceManager &SM, + SourceLocation KeyPosition) { + const FullSourceLoc FullKeyPosition(KeyPosition, SM); + std::pair<FileID, unsigned> FileIDAndOffset = + FullKeyPosition.getSpellingLoc().getDecomposedLoc(); + const FileEntry *FE = SM.getFileEntryForID(FileIDAndOffset.first); + assert(FE && "Cannot create AtomicChange with invalid location."); + FilePath = FE->getName(); + Key = FilePath + ":" + std::to_string(FileIDAndOffset.second); +} + +AtomicChange::AtomicChange(std::string Key, std::string FilePath, + std::string Error, + std::vector<std::string> InsertedHeaders, + std::vector<std::string> RemovedHeaders, + clang::tooling::Replacements Replaces) + : Key(std::move(Key)), FilePath(std::move(FilePath)), + Error(std::move(Error)), InsertedHeaders(std::move(InsertedHeaders)), + RemovedHeaders(std::move(RemovedHeaders)), Replaces(std::move(Replaces)) { +} + +bool AtomicChange::operator==(const AtomicChange &Other) const { + if (Key != Other.Key || FilePath != Other.FilePath || Error != Other.Error) + return false; + if (!(Replaces == Other.Replaces)) + return false; + // FXIME: Compare header insertions/removals. + return true; +} + +std::string AtomicChange::toYAMLString() { + std::string YamlContent; + llvm::raw_string_ostream YamlContentStream(YamlContent); + + llvm::yaml::Output YAML(YamlContentStream); + YAML << *this; + YamlContentStream.flush(); + return YamlContent; +} + +AtomicChange AtomicChange::convertFromYAML(llvm::StringRef YAMLContent) { + NormalizedAtomicChange NE; + llvm::yaml::Input YAML(YAMLContent); + YAML >> NE; + AtomicChange E(NE.Key, NE.FilePath, NE.Error, NE.InsertedHeaders, + NE.RemovedHeaders, tooling::Replacements()); + for (const auto &R : NE.Replaces) { + llvm::Error Err = E.Replaces.add(R); + if (Err) + llvm_unreachable( + "Failed to add replacement when Converting YAML to AtomicChange."); + llvm::consumeError(std::move(Err)); + } + return E; +} + +llvm::Error AtomicChange::replace(const SourceManager &SM, + const CharSourceRange &Range, + llvm::StringRef ReplacementText) { + return Replaces.add(Replacement(SM, Range, ReplacementText)); +} + +llvm::Error AtomicChange::replace(const SourceManager &SM, SourceLocation Loc, + unsigned Length, llvm::StringRef Text) { + return Replaces.add(Replacement(SM, Loc, Length, Text)); +} + +llvm::Error AtomicChange::insert(const SourceManager &SM, SourceLocation Loc, + llvm::StringRef Text, bool InsertAfter) { + if (Text.empty()) + return llvm::Error::success(); + Replacement R(SM, Loc, 0, Text); + llvm::Error Err = Replaces.add(R); + if (Err) { + return llvm::handleErrors( + std::move(Err), [&](const ReplacementError &RE) -> llvm::Error { + if (RE.get() != replacement_error::insert_conflict) + return llvm::make_error<ReplacementError>(RE); + unsigned NewOffset = Replaces.getShiftedCodePosition(R.getOffset()); + if (!InsertAfter) + NewOffset -= + RE.getExistingReplacement()->getReplacementText().size(); + Replacement NewR(R.getFilePath(), NewOffset, 0, Text); + Replaces = Replaces.merge(Replacements(NewR)); + return llvm::Error::success(); + }); + } + return llvm::Error::success(); +} + +void AtomicChange::addHeader(llvm::StringRef Header) { + InsertedHeaders.push_back(Header); +} + +void AtomicChange::removeHeader(llvm::StringRef Header) { + RemovedHeaders.push_back(Header); +} + +llvm::Expected<std::string> +applyAtomicChanges(llvm::StringRef FilePath, llvm::StringRef Code, + llvm::ArrayRef<AtomicChange> Changes, + const ApplyChangesSpec &Spec) { + llvm::Expected<Replacements> HeaderReplacements = + createReplacementsForHeaders(FilePath, Code, Changes, Spec.Style); + if (!HeaderReplacements) + return make_string_error( + "Failed to create replacements for header changes: " + + llvm::toString(HeaderReplacements.takeError())); + + llvm::Expected<Replacements> Replaces = + combineReplacementsInChanges(FilePath, Changes); + if (!Replaces) + return make_string_error("Failed to combine replacements in all changes: " + + llvm::toString(Replaces.takeError())); + + Replacements AllReplaces = std::move(*Replaces); + for (const auto &R : *HeaderReplacements) { + llvm::Error Err = AllReplaces.add(R); + if (Err) + return make_string_error( + "Failed to combine existing replacements with header replacements: " + + llvm::toString(std::move(Err))); + } + + if (Spec.Cleanup) { + llvm::Expected<Replacements> CleanReplaces = + format::cleanupAroundReplacements(Code, AllReplaces, Spec.Style); + if (!CleanReplaces) + return make_string_error("Failed to cleanup around replacements: " + + llvm::toString(CleanReplaces.takeError())); + AllReplaces = std::move(*CleanReplaces); + } + + // Apply all replacements. + llvm::Expected<std::string> ChangedCode = + applyAllReplacements(Code, AllReplaces); + if (!ChangedCode) + return make_string_error("Failed to apply all replacements: " + + llvm::toString(ChangedCode.takeError())); + + // Sort inserted headers. This is done even if other formatting is turned off + // as incorrectly sorted headers are always just wrong, it's not a matter of + // taste. + Replacements HeaderSortingReplacements = format::sortIncludes( + Spec.Style, *ChangedCode, AllReplaces.getAffectedRanges(), FilePath); + ChangedCode = applyAllReplacements(*ChangedCode, HeaderSortingReplacements); + if (!ChangedCode) + return make_string_error( + "Failed to apply replacements for sorting includes: " + + llvm::toString(ChangedCode.takeError())); + + AllReplaces = AllReplaces.merge(HeaderSortingReplacements); + + std::vector<Range> FormatRanges = getRangesForFormating( + *ChangedCode, Spec.Style.ColumnLimit, Spec.Format, AllReplaces); + if (!FormatRanges.empty()) { + Replacements FormatReplacements = + format::reformat(Spec.Style, *ChangedCode, FormatRanges, FilePath); + ChangedCode = applyAllReplacements(*ChangedCode, FormatReplacements); + if (!ChangedCode) + return make_string_error( + "Failed to apply replacements for formatting changed code: " + + llvm::toString(ChangedCode.takeError())); + } + return ChangedCode; +} + +} // end namespace tooling +} // end namespace clang diff --git a/contrib/llvm/tools/clang/lib/Tooling/Refactoring/Extract/Extract.cpp b/contrib/llvm/tools/clang/lib/Tooling/Refactoring/Extract/Extract.cpp new file mode 100644 index 000000000000..7a741bdb2e91 --- /dev/null +++ b/contrib/llvm/tools/clang/lib/Tooling/Refactoring/Extract/Extract.cpp @@ -0,0 +1,199 @@ +//===--- Extract.cpp - Clang refactoring library --------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +/// +/// \file +/// Implements the "extract" refactoring that can pull code into +/// new functions, methods or declare new variables. +/// +//===----------------------------------------------------------------------===// + +#include "clang/Tooling/Refactoring/Extract/Extract.h" +#include "SourceExtraction.h" +#include "clang/AST/ASTContext.h" +#include "clang/AST/DeclCXX.h" +#include "clang/AST/Expr.h" +#include "clang/AST/ExprObjC.h" +#include "clang/Rewrite/Core/Rewriter.h" + +namespace clang { +namespace tooling { + +namespace { + +/// Returns true if \c E is a simple literal or a reference expression that +/// should not be extracted. +bool isSimpleExpression(const Expr *E) { + if (!E) + return false; + switch (E->IgnoreParenCasts()->getStmtClass()) { + case Stmt::DeclRefExprClass: + case Stmt::PredefinedExprClass: + case Stmt::IntegerLiteralClass: + case Stmt::FloatingLiteralClass: + case Stmt::ImaginaryLiteralClass: + case Stmt::CharacterLiteralClass: + case Stmt::StringLiteralClass: + return true; + default: + return false; + } +} + +SourceLocation computeFunctionExtractionLocation(const Decl *D) { + if (isa<CXXMethodDecl>(D)) { + // Code from method that is defined in class body should be extracted to a + // function defined just before the class. + while (const auto *RD = dyn_cast<CXXRecordDecl>(D->getLexicalDeclContext())) + D = RD; + } + return D->getBeginLoc(); +} + +} // end anonymous namespace + +const RefactoringDescriptor &ExtractFunction::describe() { + static const RefactoringDescriptor Descriptor = { + "extract-function", + "Extract Function", + "(WIP action; use with caution!) Extracts code into a new function", + }; + return Descriptor; +} + +Expected<ExtractFunction> +ExtractFunction::initiate(RefactoringRuleContext &Context, + CodeRangeASTSelection Code, + Optional<std::string> DeclName) { + // We would like to extract code out of functions/methods/blocks. + // Prohibit extraction from things like global variable / field + // initializers and other top-level expressions. + if (!Code.isInFunctionLikeBodyOfCode()) + return Context.createDiagnosticError( + diag::err_refactor_code_outside_of_function); + + if (Code.size() == 1) { + // Avoid extraction of simple literals and references. + if (isSimpleExpression(dyn_cast<Expr>(Code[0]))) + return Context.createDiagnosticError( + diag::err_refactor_extract_simple_expression); + + // Property setters can't be extracted. + if (const auto *PRE = dyn_cast<ObjCPropertyRefExpr>(Code[0])) { + if (!PRE->isMessagingGetter()) + return Context.createDiagnosticError( + diag::err_refactor_extract_prohibited_expression); + } + } + + return ExtractFunction(std::move(Code), DeclName); +} + +// FIXME: Support C++ method extraction. +// FIXME: Support Objective-C method extraction. +Expected<AtomicChanges> +ExtractFunction::createSourceReplacements(RefactoringRuleContext &Context) { + const Decl *ParentDecl = Code.getFunctionLikeNearestParent(); + assert(ParentDecl && "missing parent"); + + // Compute the source range of the code that should be extracted. + SourceRange ExtractedRange(Code[0]->getBeginLoc(), + Code[Code.size() - 1]->getEndLoc()); + // FIXME (Alex L): Add code that accounts for macro locations. + + ASTContext &AST = Context.getASTContext(); + SourceManager &SM = AST.getSourceManager(); + const LangOptions &LangOpts = AST.getLangOpts(); + Rewriter ExtractedCodeRewriter(SM, LangOpts); + + // FIXME: Capture used variables. + + // Compute the return type. + QualType ReturnType = AST.VoidTy; + // FIXME (Alex L): Account for the return statement in extracted code. + // FIXME (Alex L): Check for lexical expression instead. + bool IsExpr = Code.size() == 1 && isa<Expr>(Code[0]); + if (IsExpr) { + // FIXME (Alex L): Get a more user-friendly type if needed. + ReturnType = cast<Expr>(Code[0])->getType(); + } + + // FIXME: Rewrite the extracted code performing any required adjustments. + + // FIXME: Capture any field if necessary (method -> function extraction). + + // FIXME: Sort captured variables by name. + + // FIXME: Capture 'this' / 'self' if necessary. + + // FIXME: Compute the actual parameter types. + + // Compute the location of the extracted declaration. + SourceLocation ExtractedDeclLocation = + computeFunctionExtractionLocation(ParentDecl); + // FIXME: Adjust the location to account for any preceding comments. + + // FIXME: Adjust with PP awareness like in Sema to get correct 'bool' + // treatment. + PrintingPolicy PP = AST.getPrintingPolicy(); + // FIXME: PP.UseStdFunctionForLambda = true; + PP.SuppressStrongLifetime = true; + PP.SuppressLifetimeQualifiers = true; + PP.SuppressUnwrittenScope = true; + + ExtractionSemicolonPolicy Semicolons = ExtractionSemicolonPolicy::compute( + Code[Code.size() - 1], ExtractedRange, SM, LangOpts); + AtomicChange Change(SM, ExtractedDeclLocation); + // Create the replacement for the extracted declaration. + { + std::string ExtractedCode; + llvm::raw_string_ostream OS(ExtractedCode); + // FIXME: Use 'inline' in header. + OS << "static "; + ReturnType.print(OS, PP, DeclName); + OS << '('; + // FIXME: Arguments. + OS << ')'; + + // Function body. + OS << " {\n"; + if (IsExpr && !ReturnType->isVoidType()) + OS << "return "; + OS << ExtractedCodeRewriter.getRewrittenText(ExtractedRange); + if (Semicolons.isNeededInExtractedFunction()) + OS << ';'; + OS << "\n}\n\n"; + auto Err = Change.insert(SM, ExtractedDeclLocation, OS.str()); + if (Err) + return std::move(Err); + } + + // Create the replacement for the call to the extracted declaration. + { + std::string ReplacedCode; + llvm::raw_string_ostream OS(ReplacedCode); + + OS << DeclName << '('; + // FIXME: Forward arguments. + OS << ')'; + if (Semicolons.isNeededInOriginalFunction()) + OS << ';'; + + auto Err = Change.replace( + SM, CharSourceRange::getTokenRange(ExtractedRange), OS.str()); + if (Err) + return std::move(Err); + } + + // FIXME: Add support for assocciated symbol location to AtomicChange to mark + // the ranges of the name of the extracted declaration. + return AtomicChanges{std::move(Change)}; +} + +} // end namespace tooling +} // end namespace clang diff --git a/contrib/llvm/tools/clang/lib/Tooling/Refactoring/Extract/SourceExtraction.cpp b/contrib/llvm/tools/clang/lib/Tooling/Refactoring/Extract/SourceExtraction.cpp new file mode 100644 index 000000000000..7fd8cc2d3c7f --- /dev/null +++ b/contrib/llvm/tools/clang/lib/Tooling/Refactoring/Extract/SourceExtraction.cpp @@ -0,0 +1,112 @@ +//===--- SourceExtraction.cpp - Clang refactoring library -----------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "SourceExtraction.h" +#include "clang/AST/Stmt.h" +#include "clang/AST/StmtCXX.h" +#include "clang/AST/StmtObjC.h" +#include "clang/Basic/SourceManager.h" +#include "clang/Lex/Lexer.h" + +using namespace clang; + +namespace { + +/// Returns true if the token at the given location is a semicolon. +bool isSemicolonAtLocation(SourceLocation TokenLoc, const SourceManager &SM, + const LangOptions &LangOpts) { + return Lexer::getSourceText( + CharSourceRange::getTokenRange(TokenLoc, TokenLoc), SM, + LangOpts) == ";"; +} + +/// Returns true if there should be a semicolon after the given statement. +bool isSemicolonRequiredAfter(const Stmt *S) { + if (isa<CompoundStmt>(S)) + return false; + if (const auto *If = dyn_cast<IfStmt>(S)) + return isSemicolonRequiredAfter(If->getElse() ? If->getElse() + : If->getThen()); + if (const auto *While = dyn_cast<WhileStmt>(S)) + return isSemicolonRequiredAfter(While->getBody()); + if (const auto *For = dyn_cast<ForStmt>(S)) + return isSemicolonRequiredAfter(For->getBody()); + if (const auto *CXXFor = dyn_cast<CXXForRangeStmt>(S)) + return isSemicolonRequiredAfter(CXXFor->getBody()); + if (const auto *ObjCFor = dyn_cast<ObjCForCollectionStmt>(S)) + return isSemicolonRequiredAfter(ObjCFor->getBody()); + switch (S->getStmtClass()) { + case Stmt::SwitchStmtClass: + case Stmt::CXXTryStmtClass: + case Stmt::ObjCAtSynchronizedStmtClass: + case Stmt::ObjCAutoreleasePoolStmtClass: + case Stmt::ObjCAtTryStmtClass: + return false; + default: + return true; + } +} + +/// Returns true if the two source locations are on the same line. +bool areOnSameLine(SourceLocation Loc1, SourceLocation Loc2, + const SourceManager &SM) { + return !Loc1.isMacroID() && !Loc2.isMacroID() && + SM.getSpellingLineNumber(Loc1) == SM.getSpellingLineNumber(Loc2); +} + +} // end anonymous namespace + +namespace clang { +namespace tooling { + +ExtractionSemicolonPolicy +ExtractionSemicolonPolicy::compute(const Stmt *S, SourceRange &ExtractedRange, + const SourceManager &SM, + const LangOptions &LangOpts) { + auto neededInExtractedFunction = []() { + return ExtractionSemicolonPolicy(true, false); + }; + auto neededInOriginalFunction = []() { + return ExtractionSemicolonPolicy(false, true); + }; + + /// The extracted expression should be terminated with a ';'. The call to + /// the extracted function will replace this expression, so it won't need + /// a terminating ';'. + if (isa<Expr>(S)) + return neededInExtractedFunction(); + + /// Some statements don't need to be terminated with ';'. The call to the + /// extracted function will be a standalone statement, so it should be + /// terminated with a ';'. + bool NeedsSemi = isSemicolonRequiredAfter(S); + if (!NeedsSemi) + return neededInOriginalFunction(); + + /// Some statements might end at ';'. The extraction will move that ';', so + /// the call to the extracted function should be terminated with a ';'. + SourceLocation End = ExtractedRange.getEnd(); + if (isSemicolonAtLocation(End, SM, LangOpts)) + return neededInOriginalFunction(); + + /// Other statements should generally have a trailing ';'. We can try to find + /// it and move it together it with the extracted code. + Optional<Token> NextToken = Lexer::findNextToken(End, SM, LangOpts); + if (NextToken && NextToken->is(tok::semi) && + areOnSameLine(NextToken->getLocation(), End, SM)) { + ExtractedRange.setEnd(NextToken->getLocation()); + return neededInOriginalFunction(); + } + + /// Otherwise insert semicolons in both places. + return ExtractionSemicolonPolicy(true, true); +} + +} // end namespace tooling +} // end namespace clang diff --git a/contrib/llvm/tools/clang/lib/Tooling/Refactoring/Extract/SourceExtraction.h b/contrib/llvm/tools/clang/lib/Tooling/Refactoring/Extract/SourceExtraction.h new file mode 100644 index 000000000000..4b4bd8b477ff --- /dev/null +++ b/contrib/llvm/tools/clang/lib/Tooling/Refactoring/Extract/SourceExtraction.h @@ -0,0 +1,52 @@ +//===--- SourceExtraction.cpp - Clang refactoring library -----------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_LIB_TOOLING_REFACTORING_EXTRACT_SOURCE_EXTRACTION_H +#define LLVM_CLANG_LIB_TOOLING_REFACTORING_EXTRACT_SOURCE_EXTRACTION_H + +#include "clang/Basic/LLVM.h" + +namespace clang { + +class LangOptions; +class SourceManager; +class SourceRange; +class Stmt; + +namespace tooling { + +/// Determines which semicolons should be inserted during extraction. +class ExtractionSemicolonPolicy { +public: + bool isNeededInExtractedFunction() const { + return IsNeededInExtractedFunction; + } + + bool isNeededInOriginalFunction() const { return IsNeededInOriginalFunction; } + + /// Returns the semicolon insertion policy that is needed for extraction of + /// the given statement from the given source range. + static ExtractionSemicolonPolicy compute(const Stmt *S, + SourceRange &ExtractedRange, + const SourceManager &SM, + const LangOptions &LangOpts); + +private: + ExtractionSemicolonPolicy(bool IsNeededInExtractedFunction, + bool IsNeededInOriginalFunction) + : IsNeededInExtractedFunction(IsNeededInExtractedFunction), + IsNeededInOriginalFunction(IsNeededInOriginalFunction) {} + bool IsNeededInExtractedFunction; + bool IsNeededInOriginalFunction; +}; + +} // end namespace tooling +} // end namespace clang + +#endif // LLVM_CLANG_LIB_TOOLING_REFACTORING_EXTRACT_SOURCE_EXTRACTION_H diff --git a/contrib/llvm/tools/clang/lib/Tooling/Refactoring/RefactoringActions.cpp b/contrib/llvm/tools/clang/lib/Tooling/Refactoring/RefactoringActions.cpp new file mode 100644 index 000000000000..37a1639cb446 --- /dev/null +++ b/contrib/llvm/tools/clang/lib/Tooling/Refactoring/RefactoringActions.cpp @@ -0,0 +1,114 @@ +//===--- RefactoringActions.cpp - Constructs refactoring actions ----------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "clang/Tooling/Refactoring/Extract/Extract.h" +#include "clang/Tooling/Refactoring/RefactoringAction.h" +#include "clang/Tooling/Refactoring/RefactoringOptions.h" +#include "clang/Tooling/Refactoring/Rename/RenamingAction.h" + +namespace clang { +namespace tooling { + +namespace { + +class DeclNameOption final : public OptionalRefactoringOption<std::string> { +public: + StringRef getName() const { return "name"; } + StringRef getDescription() const { + return "Name of the extracted declaration"; + } +}; + +// FIXME: Rewrite the Actions to avoid duplication of descriptions/names with +// rules. +class ExtractRefactoring final : public RefactoringAction { +public: + StringRef getCommand() const override { return "extract"; } + + StringRef getDescription() const override { + return "(WIP action; use with caution!) Extracts code into a new function"; + } + + /// Returns a set of refactoring actions rules that are defined by this + /// action. + RefactoringActionRules createActionRules() const override { + RefactoringActionRules Rules; + Rules.push_back(createRefactoringActionRule<ExtractFunction>( + CodeRangeASTSelectionRequirement(), + OptionRequirement<DeclNameOption>())); + return Rules; + } +}; + +class OldQualifiedNameOption : public RequiredRefactoringOption<std::string> { +public: + StringRef getName() const override { return "old-qualified-name"; } + StringRef getDescription() const override { + return "The old qualified name to be renamed"; + } +}; + +class NewQualifiedNameOption : public RequiredRefactoringOption<std::string> { +public: + StringRef getName() const override { return "new-qualified-name"; } + StringRef getDescription() const override { + return "The new qualified name to change the symbol to"; + } +}; + +class NewNameOption : public RequiredRefactoringOption<std::string> { +public: + StringRef getName() const override { return "new-name"; } + StringRef getDescription() const override { + return "The new name to change the symbol to"; + } +}; + +// FIXME: Rewrite the Actions to avoid duplication of descriptions/names with +// rules. +class LocalRename final : public RefactoringAction { +public: + StringRef getCommand() const override { return "local-rename"; } + + StringRef getDescription() const override { + return "Finds and renames symbols in code with no indexer support"; + } + + /// Returns a set of refactoring actions rules that are defined by this + /// action. + RefactoringActionRules createActionRules() const override { + RefactoringActionRules Rules; + Rules.push_back(createRefactoringActionRule<RenameOccurrences>( + SourceRangeSelectionRequirement(), OptionRequirement<NewNameOption>())); + // FIXME: Use NewNameOption. + Rules.push_back(createRefactoringActionRule<QualifiedRenameRule>( + OptionRequirement<OldQualifiedNameOption>(), + OptionRequirement<NewQualifiedNameOption>())); + return Rules; + } +}; + +} // end anonymous namespace + +std::vector<std::unique_ptr<RefactoringAction>> createRefactoringActions() { + std::vector<std::unique_ptr<RefactoringAction>> Actions; + + Actions.push_back(llvm::make_unique<LocalRename>()); + Actions.push_back(llvm::make_unique<ExtractRefactoring>()); + + return Actions; +} + +RefactoringActionRules RefactoringAction::createActiveActionRules() { + // FIXME: Filter out rules that are not supported by a particular client. + return createActionRules(); +} + +} // end namespace tooling +} // end namespace clang diff --git a/contrib/llvm/tools/clang/lib/Tooling/Refactoring/Rename/RenamingAction.cpp b/contrib/llvm/tools/clang/lib/Tooling/Refactoring/Rename/RenamingAction.cpp new file mode 100644 index 000000000000..44ffae90efa7 --- /dev/null +++ b/contrib/llvm/tools/clang/lib/Tooling/Refactoring/Rename/RenamingAction.cpp @@ -0,0 +1,277 @@ +//===--- RenamingAction.cpp - Clang refactoring library -------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +/// +/// \file +/// Provides an action to rename every symbol at a point. +/// +//===----------------------------------------------------------------------===// + +#include "clang/Tooling/Refactoring/Rename/RenamingAction.h" +#include "clang/AST/ASTConsumer.h" +#include "clang/AST/ASTContext.h" +#include "clang/Basic/FileManager.h" +#include "clang/Frontend/CompilerInstance.h" +#include "clang/Frontend/FrontendAction.h" +#include "clang/Lex/Lexer.h" +#include "clang/Lex/Preprocessor.h" +#include "clang/Tooling/CommonOptionsParser.h" +#include "clang/Tooling/Refactoring.h" +#include "clang/Tooling/Refactoring/RefactoringAction.h" +#include "clang/Tooling/Refactoring/RefactoringDiagnostic.h" +#include "clang/Tooling/Refactoring/RefactoringOptions.h" +#include "clang/Tooling/Refactoring/Rename/SymbolName.h" +#include "clang/Tooling/Refactoring/Rename/USRFinder.h" +#include "clang/Tooling/Refactoring/Rename/USRFindingAction.h" +#include "clang/Tooling/Refactoring/Rename/USRLocFinder.h" +#include "clang/Tooling/Tooling.h" +#include "llvm/ADT/STLExtras.h" +#include "llvm/Support/Errc.h" +#include "llvm/Support/Error.h" +#include <string> +#include <vector> + +using namespace llvm; + +namespace clang { +namespace tooling { + +namespace { + +Expected<SymbolOccurrences> +findSymbolOccurrences(const NamedDecl *ND, RefactoringRuleContext &Context) { + std::vector<std::string> USRs = + getUSRsForDeclaration(ND, Context.getASTContext()); + std::string PrevName = ND->getNameAsString(); + return getOccurrencesOfUSRs(USRs, PrevName, + Context.getASTContext().getTranslationUnitDecl()); +} + +} // end anonymous namespace + +const RefactoringDescriptor &RenameOccurrences::describe() { + static const RefactoringDescriptor Descriptor = { + "local-rename", + "Rename", + "Finds and renames symbols in code with no indexer support", + }; + return Descriptor; +} + +Expected<RenameOccurrences> +RenameOccurrences::initiate(RefactoringRuleContext &Context, + SourceRange SelectionRange, std::string NewName) { + const NamedDecl *ND = + getNamedDeclAt(Context.getASTContext(), SelectionRange.getBegin()); + if (!ND) + return Context.createDiagnosticError( + SelectionRange.getBegin(), diag::err_refactor_selection_no_symbol); + return RenameOccurrences(getCanonicalSymbolDeclaration(ND), + std::move(NewName)); +} + +Expected<AtomicChanges> +RenameOccurrences::createSourceReplacements(RefactoringRuleContext &Context) { + Expected<SymbolOccurrences> Occurrences = findSymbolOccurrences(ND, Context); + if (!Occurrences) + return Occurrences.takeError(); + // FIXME: Verify that the new name is valid. + SymbolName Name(NewName); + return createRenameReplacements( + *Occurrences, Context.getASTContext().getSourceManager(), Name); +} + +Expected<QualifiedRenameRule> +QualifiedRenameRule::initiate(RefactoringRuleContext &Context, + std::string OldQualifiedName, + std::string NewQualifiedName) { + const NamedDecl *ND = + getNamedDeclFor(Context.getASTContext(), OldQualifiedName); + if (!ND) + return llvm::make_error<llvm::StringError>("Could not find symbol " + + OldQualifiedName, + llvm::errc::invalid_argument); + return QualifiedRenameRule(ND, std::move(NewQualifiedName)); +} + +const RefactoringDescriptor &QualifiedRenameRule::describe() { + static const RefactoringDescriptor Descriptor = { + /*Name=*/"local-qualified-rename", + /*Title=*/"Qualified Rename", + /*Description=*/ + R"(Finds and renames qualified symbols in code within a translation unit. +It is used to move/rename a symbol to a new namespace/name: + * Supported symbols: classes, class members, functions, enums, and type alias. + * Renames all symbol occurrences from the old qualified name to the new + qualified name. All symbol references will be correctly qualified; For + symbol definitions, only name will be changed. +For example, rename "A::Foo" to "B::Bar": + Old code: + namespace foo { + class A {}; + } + + namespace bar { + void f(foo::A a) {} + } + + New code after rename: + namespace foo { + class B {}; + } + + namespace bar { + void f(B b) {} + })" + }; + return Descriptor; +} + +Expected<AtomicChanges> +QualifiedRenameRule::createSourceReplacements(RefactoringRuleContext &Context) { + auto USRs = getUSRsForDeclaration(ND, Context.getASTContext()); + assert(!USRs.empty()); + return tooling::createRenameAtomicChanges( + USRs, NewQualifiedName, Context.getASTContext().getTranslationUnitDecl()); +} + +Expected<std::vector<AtomicChange>> +createRenameReplacements(const SymbolOccurrences &Occurrences, + const SourceManager &SM, const SymbolName &NewName) { + // FIXME: A true local rename can use just one AtomicChange. + std::vector<AtomicChange> Changes; + for (const auto &Occurrence : Occurrences) { + ArrayRef<SourceRange> Ranges = Occurrence.getNameRanges(); + assert(NewName.getNamePieces().size() == Ranges.size() && + "Mismatching number of ranges and name pieces"); + AtomicChange Change(SM, Ranges[0].getBegin()); + for (const auto &Range : llvm::enumerate(Ranges)) { + auto Error = + Change.replace(SM, CharSourceRange::getCharRange(Range.value()), + NewName.getNamePieces()[Range.index()]); + if (Error) + return std::move(Error); + } + Changes.push_back(std::move(Change)); + } + return std::move(Changes); +} + +/// Takes each atomic change and inserts its replacements into the set of +/// replacements that belong to the appropriate file. +static void convertChangesToFileReplacements( + ArrayRef<AtomicChange> AtomicChanges, + std::map<std::string, tooling::Replacements> *FileToReplaces) { + for (const auto &AtomicChange : AtomicChanges) { + for (const auto &Replace : AtomicChange.getReplacements()) { + llvm::Error Err = (*FileToReplaces)[Replace.getFilePath()].add(Replace); + if (Err) { + llvm::errs() << "Renaming failed in " << Replace.getFilePath() << "! " + << llvm::toString(std::move(Err)) << "\n"; + } + } + } +} + +class RenamingASTConsumer : public ASTConsumer { +public: + RenamingASTConsumer( + const std::vector<std::string> &NewNames, + const std::vector<std::string> &PrevNames, + const std::vector<std::vector<std::string>> &USRList, + std::map<std::string, tooling::Replacements> &FileToReplaces, + bool PrintLocations) + : NewNames(NewNames), PrevNames(PrevNames), USRList(USRList), + FileToReplaces(FileToReplaces), PrintLocations(PrintLocations) {} + + void HandleTranslationUnit(ASTContext &Context) override { + for (unsigned I = 0; I < NewNames.size(); ++I) { + // If the previous name was not found, ignore this rename request. + if (PrevNames[I].empty()) + continue; + + HandleOneRename(Context, NewNames[I], PrevNames[I], USRList[I]); + } + } + + void HandleOneRename(ASTContext &Context, const std::string &NewName, + const std::string &PrevName, + const std::vector<std::string> &USRs) { + const SourceManager &SourceMgr = Context.getSourceManager(); + + SymbolOccurrences Occurrences = tooling::getOccurrencesOfUSRs( + USRs, PrevName, Context.getTranslationUnitDecl()); + if (PrintLocations) { + for (const auto &Occurrence : Occurrences) { + FullSourceLoc FullLoc(Occurrence.getNameRanges()[0].getBegin(), + SourceMgr); + errs() << "clang-rename: renamed at: " << SourceMgr.getFilename(FullLoc) + << ":" << FullLoc.getSpellingLineNumber() << ":" + << FullLoc.getSpellingColumnNumber() << "\n"; + } + } + // FIXME: Support multi-piece names. + // FIXME: better error handling (propagate error out). + SymbolName NewNameRef(NewName); + Expected<std::vector<AtomicChange>> Change = + createRenameReplacements(Occurrences, SourceMgr, NewNameRef); + if (!Change) { + llvm::errs() << "Failed to create renaming replacements for '" << PrevName + << "'! " << llvm::toString(Change.takeError()) << "\n"; + return; + } + convertChangesToFileReplacements(*Change, &FileToReplaces); + } + +private: + const std::vector<std::string> &NewNames, &PrevNames; + const std::vector<std::vector<std::string>> &USRList; + std::map<std::string, tooling::Replacements> &FileToReplaces; + bool PrintLocations; +}; + +// A renamer to rename symbols which are identified by a give USRList to +// new name. +// +// FIXME: Merge with the above RenamingASTConsumer. +class USRSymbolRenamer : public ASTConsumer { +public: + USRSymbolRenamer(const std::vector<std::string> &NewNames, + const std::vector<std::vector<std::string>> &USRList, + std::map<std::string, tooling::Replacements> &FileToReplaces) + : NewNames(NewNames), USRList(USRList), FileToReplaces(FileToReplaces) { + assert(USRList.size() == NewNames.size()); + } + + void HandleTranslationUnit(ASTContext &Context) override { + for (unsigned I = 0; I < NewNames.size(); ++I) { + // FIXME: Apply AtomicChanges directly once the refactoring APIs are + // ready. + auto AtomicChanges = tooling::createRenameAtomicChanges( + USRList[I], NewNames[I], Context.getTranslationUnitDecl()); + convertChangesToFileReplacements(AtomicChanges, &FileToReplaces); + } + } + +private: + const std::vector<std::string> &NewNames; + const std::vector<std::vector<std::string>> &USRList; + std::map<std::string, tooling::Replacements> &FileToReplaces; +}; + +std::unique_ptr<ASTConsumer> RenamingAction::newASTConsumer() { + return llvm::make_unique<RenamingASTConsumer>(NewNames, PrevNames, USRList, + FileToReplaces, PrintLocations); +} + +std::unique_ptr<ASTConsumer> QualifiedRenamingAction::newASTConsumer() { + return llvm::make_unique<USRSymbolRenamer>(NewNames, USRList, FileToReplaces); +} + +} // end namespace tooling +} // end namespace clang diff --git a/contrib/llvm/tools/clang/lib/Tooling/Refactoring/Rename/SymbolOccurrences.cpp b/contrib/llvm/tools/clang/lib/Tooling/Refactoring/Rename/SymbolOccurrences.cpp new file mode 100644 index 000000000000..ea64b2c1aa8c --- /dev/null +++ b/contrib/llvm/tools/clang/lib/Tooling/Refactoring/Rename/SymbolOccurrences.cpp @@ -0,0 +1,37 @@ +//===--- SymbolOccurrences.cpp - Clang refactoring library ----------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "clang/Tooling/Refactoring/Rename/SymbolOccurrences.h" +#include "clang/Tooling/Refactoring/Rename/SymbolName.h" +#include "llvm/ADT/STLExtras.h" + +using namespace clang; +using namespace tooling; + +SymbolOccurrence::SymbolOccurrence(const SymbolName &Name, OccurrenceKind Kind, + ArrayRef<SourceLocation> Locations) + : Kind(Kind) { + ArrayRef<std::string> NamePieces = Name.getNamePieces(); + assert(Locations.size() == NamePieces.size() && + "mismatching number of locations and lengths"); + assert(!Locations.empty() && "no locations"); + if (Locations.size() == 1) { + RangeOrNumRanges = SourceRange( + Locations[0], Locations[0].getLocWithOffset(NamePieces[0].size())); + return; + } + MultipleRanges = llvm::make_unique<SourceRange[]>(Locations.size()); + RangeOrNumRanges.setBegin( + SourceLocation::getFromRawEncoding(Locations.size())); + for (const auto &Loc : llvm::enumerate(Locations)) { + MultipleRanges[Loc.index()] = SourceRange( + Loc.value(), + Loc.value().getLocWithOffset(NamePieces[Loc.index()].size())); + } +} diff --git a/contrib/llvm/tools/clang/lib/Tooling/Refactoring/Rename/USRFinder.cpp b/contrib/llvm/tools/clang/lib/Tooling/Refactoring/Rename/USRFinder.cpp new file mode 100644 index 000000000000..4ed805fd504c --- /dev/null +++ b/contrib/llvm/tools/clang/lib/Tooling/Refactoring/Rename/USRFinder.cpp @@ -0,0 +1,146 @@ +//===--- USRFinder.cpp - Clang refactoring library ------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +/// +/// \file Implements a recursive AST visitor that finds the USR of a symbol at a +/// point. +/// +//===----------------------------------------------------------------------===// + +#include "clang/Tooling/Refactoring/Rename/USRFinder.h" +#include "clang/AST/AST.h" +#include "clang/AST/ASTContext.h" +#include "clang/AST/RecursiveASTVisitor.h" +#include "clang/Index/USRGeneration.h" +#include "clang/Lex/Lexer.h" +#include "clang/Tooling/Refactoring/RecursiveSymbolVisitor.h" +#include "llvm/ADT/SmallVector.h" + +using namespace llvm; + +namespace clang { +namespace tooling { + +namespace { + +/// Recursively visits each AST node to find the symbol underneath the cursor. +class NamedDeclOccurrenceFindingVisitor + : public RecursiveSymbolVisitor<NamedDeclOccurrenceFindingVisitor> { +public: + // Finds the NamedDecl at a point in the source. + // \param Point the location in the source to search for the NamedDecl. + explicit NamedDeclOccurrenceFindingVisitor(const SourceLocation Point, + const ASTContext &Context) + : RecursiveSymbolVisitor(Context.getSourceManager(), + Context.getLangOpts()), + Point(Point), Context(Context) {} + + bool visitSymbolOccurrence(const NamedDecl *ND, + ArrayRef<SourceRange> NameRanges) { + if (!ND) + return true; + for (const auto &Range : NameRanges) { + SourceLocation Start = Range.getBegin(); + SourceLocation End = Range.getEnd(); + if (!Start.isValid() || !Start.isFileID() || !End.isValid() || + !End.isFileID() || !isPointWithin(Start, End)) + return true; + } + Result = ND; + return false; + } + + const NamedDecl *getNamedDecl() const { return Result; } + +private: + // Determines if the Point is within Start and End. + bool isPointWithin(const SourceLocation Start, const SourceLocation End) { + // FIXME: Add tests for Point == End. + return Point == Start || Point == End || + (Context.getSourceManager().isBeforeInTranslationUnit(Start, + Point) && + Context.getSourceManager().isBeforeInTranslationUnit(Point, End)); + } + + const NamedDecl *Result = nullptr; + const SourceLocation Point; // The location to find the NamedDecl. + const ASTContext &Context; +}; + +} // end anonymous namespace + +const NamedDecl *getNamedDeclAt(const ASTContext &Context, + const SourceLocation Point) { + const SourceManager &SM = Context.getSourceManager(); + NamedDeclOccurrenceFindingVisitor Visitor(Point, Context); + + // Try to be clever about pruning down the number of top-level declarations we + // see. If both start and end is either before or after the point we're + // looking for the point cannot be inside of this decl. Don't even look at it. + for (auto *CurrDecl : Context.getTranslationUnitDecl()->decls()) { + SourceLocation StartLoc = CurrDecl->getBeginLoc(); + SourceLocation EndLoc = CurrDecl->getEndLoc(); + if (StartLoc.isValid() && EndLoc.isValid() && + SM.isBeforeInTranslationUnit(StartLoc, Point) != + SM.isBeforeInTranslationUnit(EndLoc, Point)) + Visitor.TraverseDecl(CurrDecl); + } + + return Visitor.getNamedDecl(); +} + +namespace { + +/// Recursively visits each NamedDecl node to find the declaration with a +/// specific name. +class NamedDeclFindingVisitor + : public RecursiveASTVisitor<NamedDeclFindingVisitor> { +public: + explicit NamedDeclFindingVisitor(StringRef Name) : Name(Name) {} + + // We don't have to traverse the uses to find some declaration with a + // specific name, so just visit the named declarations. + bool VisitNamedDecl(const NamedDecl *ND) { + if (!ND) + return true; + // Fully qualified name is used to find the declaration. + if (Name != ND->getQualifiedNameAsString() && + Name != "::" + ND->getQualifiedNameAsString()) + return true; + Result = ND; + return false; + } + + const NamedDecl *getNamedDecl() const { return Result; } + +private: + const NamedDecl *Result = nullptr; + StringRef Name; +}; + +} // end anonymous namespace + +const NamedDecl *getNamedDeclFor(const ASTContext &Context, + const std::string &Name) { + NamedDeclFindingVisitor Visitor(Name); + Visitor.TraverseDecl(Context.getTranslationUnitDecl()); + return Visitor.getNamedDecl(); +} + +std::string getUSRForDecl(const Decl *Decl) { + llvm::SmallVector<char, 128> Buff; + + // FIXME: Add test for the nullptr case. + if (Decl == nullptr || index::generateUSRForDecl(Decl, Buff)) + return ""; + + return std::string(Buff.data(), Buff.size()); +} + +} // end namespace tooling +} // end namespace clang diff --git a/contrib/llvm/tools/clang/lib/Tooling/Refactoring/Rename/USRFindingAction.cpp b/contrib/llvm/tools/clang/lib/Tooling/Refactoring/Rename/USRFindingAction.cpp new file mode 100644 index 000000000000..2e7c9b0cc31b --- /dev/null +++ b/contrib/llvm/tools/clang/lib/Tooling/Refactoring/Rename/USRFindingAction.cpp @@ -0,0 +1,274 @@ +//===--- USRFindingAction.cpp - Clang refactoring library -----------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +/// +/// \file +/// Provides an action to find USR for the symbol at <offset>, as well as +/// all additional USRs. +/// +//===----------------------------------------------------------------------===// + +#include "clang/Tooling/Refactoring/Rename/USRFindingAction.h" +#include "clang/AST/AST.h" +#include "clang/AST/ASTConsumer.h" +#include "clang/AST/ASTContext.h" +#include "clang/AST/Decl.h" +#include "clang/AST/RecursiveASTVisitor.h" +#include "clang/Basic/FileManager.h" +#include "clang/Frontend/CompilerInstance.h" +#include "clang/Frontend/FrontendAction.h" +#include "clang/Lex/Lexer.h" +#include "clang/Lex/Preprocessor.h" +#include "clang/Tooling/CommonOptionsParser.h" +#include "clang/Tooling/Refactoring.h" +#include "clang/Tooling/Refactoring/Rename/USRFinder.h" +#include "clang/Tooling/Tooling.h" + +#include <algorithm> +#include <set> +#include <string> +#include <vector> + +using namespace llvm; + +namespace clang { +namespace tooling { + +const NamedDecl *getCanonicalSymbolDeclaration(const NamedDecl *FoundDecl) { + // If FoundDecl is a constructor or destructor, we want to instead take + // the Decl of the corresponding class. + if (const auto *CtorDecl = dyn_cast<CXXConstructorDecl>(FoundDecl)) + FoundDecl = CtorDecl->getParent(); + else if (const auto *DtorDecl = dyn_cast<CXXDestructorDecl>(FoundDecl)) + FoundDecl = DtorDecl->getParent(); + // FIXME: (Alex L): Canonicalize implicit template instantions, just like + // the indexer does it. + + // Note: please update the declaration's doc comment every time the + // canonicalization rules are changed. + return FoundDecl; +} + +namespace { +// NamedDeclFindingConsumer should delegate finding USRs of given Decl to +// AdditionalUSRFinder. AdditionalUSRFinder adds USRs of ctor and dtor if given +// Decl refers to class and adds USRs of all overridden methods if Decl refers +// to virtual method. +class AdditionalUSRFinder : public RecursiveASTVisitor<AdditionalUSRFinder> { +public: + AdditionalUSRFinder(const Decl *FoundDecl, ASTContext &Context) + : FoundDecl(FoundDecl), Context(Context) {} + + std::vector<std::string> Find() { + // Fill OverriddenMethods and PartialSpecs storages. + TraverseDecl(Context.getTranslationUnitDecl()); + if (const auto *MethodDecl = dyn_cast<CXXMethodDecl>(FoundDecl)) { + addUSRsOfOverridenFunctions(MethodDecl); + for (const auto &OverriddenMethod : OverriddenMethods) { + if (checkIfOverriddenFunctionAscends(OverriddenMethod)) + USRSet.insert(getUSRForDecl(OverriddenMethod)); + } + addUSRsOfInstantiatedMethods(MethodDecl); + } else if (const auto *RecordDecl = dyn_cast<CXXRecordDecl>(FoundDecl)) { + handleCXXRecordDecl(RecordDecl); + } else if (const auto *TemplateDecl = + dyn_cast<ClassTemplateDecl>(FoundDecl)) { + handleClassTemplateDecl(TemplateDecl); + } else { + USRSet.insert(getUSRForDecl(FoundDecl)); + } + return std::vector<std::string>(USRSet.begin(), USRSet.end()); + } + + bool shouldVisitTemplateInstantiations() const { return true; } + + bool VisitCXXMethodDecl(const CXXMethodDecl *MethodDecl) { + if (MethodDecl->isVirtual()) + OverriddenMethods.push_back(MethodDecl); + if (MethodDecl->getInstantiatedFromMemberFunction()) + InstantiatedMethods.push_back(MethodDecl); + return true; + } + + bool VisitClassTemplatePartialSpecializationDecl( + const ClassTemplatePartialSpecializationDecl *PartialSpec) { + PartialSpecs.push_back(PartialSpec); + return true; + } + +private: + void handleCXXRecordDecl(const CXXRecordDecl *RecordDecl) { + RecordDecl = RecordDecl->getDefinition(); + if (const auto *ClassTemplateSpecDecl = + dyn_cast<ClassTemplateSpecializationDecl>(RecordDecl)) + handleClassTemplateDecl(ClassTemplateSpecDecl->getSpecializedTemplate()); + addUSRsOfCtorDtors(RecordDecl); + } + + void handleClassTemplateDecl(const ClassTemplateDecl *TemplateDecl) { + for (const auto *Specialization : TemplateDecl->specializations()) + addUSRsOfCtorDtors(Specialization); + + for (const auto *PartialSpec : PartialSpecs) { + if (PartialSpec->getSpecializedTemplate() == TemplateDecl) + addUSRsOfCtorDtors(PartialSpec); + } + addUSRsOfCtorDtors(TemplateDecl->getTemplatedDecl()); + } + + void addUSRsOfCtorDtors(const CXXRecordDecl *RecordDecl) { + RecordDecl = RecordDecl->getDefinition(); + + // Skip if the CXXRecordDecl doesn't have definition. + if (!RecordDecl) + return; + + for (const auto *CtorDecl : RecordDecl->ctors()) + USRSet.insert(getUSRForDecl(CtorDecl)); + + USRSet.insert(getUSRForDecl(RecordDecl->getDestructor())); + USRSet.insert(getUSRForDecl(RecordDecl)); + } + + void addUSRsOfOverridenFunctions(const CXXMethodDecl *MethodDecl) { + USRSet.insert(getUSRForDecl(MethodDecl)); + // Recursively visit each OverridenMethod. + for (const auto &OverriddenMethod : MethodDecl->overridden_methods()) + addUSRsOfOverridenFunctions(OverriddenMethod); + } + + void addUSRsOfInstantiatedMethods(const CXXMethodDecl *MethodDecl) { + // For renaming a class template method, all references of the instantiated + // member methods should be renamed too, so add USRs of the instantiated + // methods to the USR set. + USRSet.insert(getUSRForDecl(MethodDecl)); + if (const auto *FT = MethodDecl->getInstantiatedFromMemberFunction()) + USRSet.insert(getUSRForDecl(FT)); + for (const auto *Method : InstantiatedMethods) { + if (USRSet.find(getUSRForDecl( + Method->getInstantiatedFromMemberFunction())) != USRSet.end()) + USRSet.insert(getUSRForDecl(Method)); + } + } + + bool checkIfOverriddenFunctionAscends(const CXXMethodDecl *MethodDecl) { + for (const auto &OverriddenMethod : MethodDecl->overridden_methods()) { + if (USRSet.find(getUSRForDecl(OverriddenMethod)) != USRSet.end()) + return true; + return checkIfOverriddenFunctionAscends(OverriddenMethod); + } + return false; + } + + const Decl *FoundDecl; + ASTContext &Context; + std::set<std::string> USRSet; + std::vector<const CXXMethodDecl *> OverriddenMethods; + std::vector<const CXXMethodDecl *> InstantiatedMethods; + std::vector<const ClassTemplatePartialSpecializationDecl *> PartialSpecs; +}; +} // namespace + +std::vector<std::string> getUSRsForDeclaration(const NamedDecl *ND, + ASTContext &Context) { + AdditionalUSRFinder Finder(ND, Context); + return Finder.Find(); +} + +class NamedDeclFindingConsumer : public ASTConsumer { +public: + NamedDeclFindingConsumer(ArrayRef<unsigned> SymbolOffsets, + ArrayRef<std::string> QualifiedNames, + std::vector<std::string> &SpellingNames, + std::vector<std::vector<std::string>> &USRList, + bool Force, bool &ErrorOccurred) + : SymbolOffsets(SymbolOffsets), QualifiedNames(QualifiedNames), + SpellingNames(SpellingNames), USRList(USRList), Force(Force), + ErrorOccurred(ErrorOccurred) {} + +private: + bool FindSymbol(ASTContext &Context, const SourceManager &SourceMgr, + unsigned SymbolOffset, const std::string &QualifiedName) { + DiagnosticsEngine &Engine = Context.getDiagnostics(); + const FileID MainFileID = SourceMgr.getMainFileID(); + + if (SymbolOffset >= SourceMgr.getFileIDSize(MainFileID)) { + ErrorOccurred = true; + unsigned InvalidOffset = Engine.getCustomDiagID( + DiagnosticsEngine::Error, + "SourceLocation in file %0 at offset %1 is invalid"); + Engine.Report(SourceLocation(), InvalidOffset) + << SourceMgr.getFileEntryForID(MainFileID)->getName() << SymbolOffset; + return false; + } + + const SourceLocation Point = SourceMgr.getLocForStartOfFile(MainFileID) + .getLocWithOffset(SymbolOffset); + const NamedDecl *FoundDecl = QualifiedName.empty() + ? getNamedDeclAt(Context, Point) + : getNamedDeclFor(Context, QualifiedName); + + if (FoundDecl == nullptr) { + if (QualifiedName.empty()) { + FullSourceLoc FullLoc(Point, SourceMgr); + unsigned CouldNotFindSymbolAt = Engine.getCustomDiagID( + DiagnosticsEngine::Error, + "clang-rename could not find symbol (offset %0)"); + Engine.Report(Point, CouldNotFindSymbolAt) << SymbolOffset; + ErrorOccurred = true; + return false; + } + + if (Force) { + SpellingNames.push_back(std::string()); + USRList.push_back(std::vector<std::string>()); + return true; + } + + unsigned CouldNotFindSymbolNamed = Engine.getCustomDiagID( + DiagnosticsEngine::Error, "clang-rename could not find symbol %0"); + Engine.Report(CouldNotFindSymbolNamed) << QualifiedName; + ErrorOccurred = true; + return false; + } + + FoundDecl = getCanonicalSymbolDeclaration(FoundDecl); + SpellingNames.push_back(FoundDecl->getNameAsString()); + AdditionalUSRFinder Finder(FoundDecl, Context); + USRList.push_back(Finder.Find()); + return true; + } + + void HandleTranslationUnit(ASTContext &Context) override { + const SourceManager &SourceMgr = Context.getSourceManager(); + for (unsigned Offset : SymbolOffsets) { + if (!FindSymbol(Context, SourceMgr, Offset, "")) + return; + } + for (const std::string &QualifiedName : QualifiedNames) { + if (!FindSymbol(Context, SourceMgr, 0, QualifiedName)) + return; + } + } + + ArrayRef<unsigned> SymbolOffsets; + ArrayRef<std::string> QualifiedNames; + std::vector<std::string> &SpellingNames; + std::vector<std::vector<std::string>> &USRList; + bool Force; + bool &ErrorOccurred; +}; + +std::unique_ptr<ASTConsumer> USRFindingAction::newASTConsumer() { + return llvm::make_unique<NamedDeclFindingConsumer>( + SymbolOffsets, QualifiedNames, SpellingNames, USRList, Force, + ErrorOccurred); +} + +} // end namespace tooling +} // end namespace clang diff --git a/contrib/llvm/tools/clang/lib/Tooling/Refactoring/Rename/USRLocFinder.cpp b/contrib/llvm/tools/clang/lib/Tooling/Refactoring/Rename/USRLocFinder.cpp new file mode 100644 index 000000000000..7f60cf54c8ec --- /dev/null +++ b/contrib/llvm/tools/clang/lib/Tooling/Refactoring/Rename/USRLocFinder.cpp @@ -0,0 +1,585 @@ +//===--- USRLocFinder.cpp - Clang refactoring library ---------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +/// +/// \file +/// Methods for finding all instances of a USR. Our strategy is very +/// simple; we just compare the USR at every relevant AST node with the one +/// provided. +/// +//===----------------------------------------------------------------------===// + +#include "clang/Tooling/Refactoring/Rename/USRLocFinder.h" +#include "clang/AST/ASTContext.h" +#include "clang/AST/RecursiveASTVisitor.h" +#include "clang/Basic/LLVM.h" +#include "clang/Basic/SourceLocation.h" +#include "clang/Basic/SourceManager.h" +#include "clang/Lex/Lexer.h" +#include "clang/Tooling/Core/Lookup.h" +#include "clang/Tooling/Refactoring/RecursiveSymbolVisitor.h" +#include "clang/Tooling/Refactoring/Rename/SymbolName.h" +#include "clang/Tooling/Refactoring/Rename/USRFinder.h" +#include "llvm/ADT/StringRef.h" +#include "llvm/Support/Casting.h" +#include <cstddef> +#include <set> +#include <string> +#include <vector> + +using namespace llvm; + +namespace clang { +namespace tooling { + +namespace { + +// Returns true if the given Loc is valid for edit. We don't edit the +// SourceLocations that are valid or in temporary buffer. +bool IsValidEditLoc(const clang::SourceManager& SM, clang::SourceLocation Loc) { + if (Loc.isInvalid()) + return false; + const clang::FullSourceLoc FullLoc(Loc, SM); + std::pair<clang::FileID, unsigned> FileIdAndOffset = + FullLoc.getSpellingLoc().getDecomposedLoc(); + return SM.getFileEntryForID(FileIdAndOffset.first) != nullptr; +} + +// This visitor recursively searches for all instances of a USR in a +// translation unit and stores them for later usage. +class USRLocFindingASTVisitor + : public RecursiveSymbolVisitor<USRLocFindingASTVisitor> { +public: + explicit USRLocFindingASTVisitor(const std::vector<std::string> &USRs, + StringRef PrevName, + const ASTContext &Context) + : RecursiveSymbolVisitor(Context.getSourceManager(), + Context.getLangOpts()), + USRSet(USRs.begin(), USRs.end()), PrevName(PrevName), Context(Context) { + } + + bool visitSymbolOccurrence(const NamedDecl *ND, + ArrayRef<SourceRange> NameRanges) { + if (USRSet.find(getUSRForDecl(ND)) != USRSet.end()) { + assert(NameRanges.size() == 1 && + "Multiple name pieces are not supported yet!"); + SourceLocation Loc = NameRanges[0].getBegin(); + const SourceManager &SM = Context.getSourceManager(); + // TODO: Deal with macro occurrences correctly. + if (Loc.isMacroID()) + Loc = SM.getSpellingLoc(Loc); + checkAndAddLocation(Loc); + } + return true; + } + + // Non-visitors: + + /// Returns a set of unique symbol occurrences. Duplicate or + /// overlapping occurrences are erroneous and should be reported! + SymbolOccurrences takeOccurrences() { return std::move(Occurrences); } + +private: + void checkAndAddLocation(SourceLocation Loc) { + const SourceLocation BeginLoc = Loc; + const SourceLocation EndLoc = Lexer::getLocForEndOfToken( + BeginLoc, 0, Context.getSourceManager(), Context.getLangOpts()); + StringRef TokenName = + Lexer::getSourceText(CharSourceRange::getTokenRange(BeginLoc, EndLoc), + Context.getSourceManager(), Context.getLangOpts()); + size_t Offset = TokenName.find(PrevName.getNamePieces()[0]); + + // The token of the source location we find actually has the old + // name. + if (Offset != StringRef::npos) + Occurrences.emplace_back(PrevName, SymbolOccurrence::MatchingSymbol, + BeginLoc.getLocWithOffset(Offset)); + } + + const std::set<std::string> USRSet; + const SymbolName PrevName; + SymbolOccurrences Occurrences; + const ASTContext &Context; +}; + +SourceLocation StartLocationForType(TypeLoc TL) { + // For elaborated types (e.g. `struct a::A`) we want the portion after the + // `struct` but including the namespace qualifier, `a::`. + if (auto ElaboratedTypeLoc = TL.getAs<clang::ElaboratedTypeLoc>()) { + NestedNameSpecifierLoc NestedNameSpecifier = + ElaboratedTypeLoc.getQualifierLoc(); + if (NestedNameSpecifier.getNestedNameSpecifier()) + return NestedNameSpecifier.getBeginLoc(); + TL = TL.getNextTypeLoc(); + } + return TL.getBeginLoc(); +} + +SourceLocation EndLocationForType(TypeLoc TL) { + // Dig past any namespace or keyword qualifications. + while (TL.getTypeLocClass() == TypeLoc::Elaborated || + TL.getTypeLocClass() == TypeLoc::Qualified) + TL = TL.getNextTypeLoc(); + + // The location for template specializations (e.g. Foo<int>) includes the + // templated types in its location range. We want to restrict this to just + // before the `<` character. + if (TL.getTypeLocClass() == TypeLoc::TemplateSpecialization) { + return TL.castAs<TemplateSpecializationTypeLoc>() + .getLAngleLoc() + .getLocWithOffset(-1); + } + return TL.getEndLoc(); +} + +NestedNameSpecifier *GetNestedNameForType(TypeLoc TL) { + // Dig past any keyword qualifications. + while (TL.getTypeLocClass() == TypeLoc::Qualified) + TL = TL.getNextTypeLoc(); + + // For elaborated types (e.g. `struct a::A`) we want the portion after the + // `struct` but including the namespace qualifier, `a::`. + if (auto ElaboratedTypeLoc = TL.getAs<clang::ElaboratedTypeLoc>()) + return ElaboratedTypeLoc.getQualifierLoc().getNestedNameSpecifier(); + return nullptr; +} + +// Find all locations identified by the given USRs for rename. +// +// This class will traverse the AST and find every AST node whose USR is in the +// given USRs' set. +class RenameLocFinder : public RecursiveASTVisitor<RenameLocFinder> { +public: + RenameLocFinder(llvm::ArrayRef<std::string> USRs, ASTContext &Context) + : USRSet(USRs.begin(), USRs.end()), Context(Context) {} + + // A structure records all information of a symbol reference being renamed. + // We try to add as few prefix qualifiers as possible. + struct RenameInfo { + // The begin location of a symbol being renamed. + SourceLocation Begin; + // The end location of a symbol being renamed. + SourceLocation End; + // The declaration of a symbol being renamed (can be nullptr). + const NamedDecl *FromDecl; + // The declaration in which the nested name is contained (can be nullptr). + const Decl *Context; + // The nested name being replaced (can be nullptr). + const NestedNameSpecifier *Specifier; + // Determine whether the prefix qualifiers of the NewName should be ignored. + // Normally, we set it to true for the symbol declaration and definition to + // avoid adding prefix qualifiers. + // For example, if it is true and NewName is "a::b::foo", then the symbol + // occurrence which the RenameInfo points to will be renamed to "foo". + bool IgnorePrefixQualifers; + }; + + bool VisitNamedDecl(const NamedDecl *Decl) { + // UsingDecl has been handled in other place. + if (llvm::isa<UsingDecl>(Decl)) + return true; + + // DestructorDecl has been handled in Typeloc. + if (llvm::isa<CXXDestructorDecl>(Decl)) + return true; + + if (Decl->isImplicit()) + return true; + + if (isInUSRSet(Decl)) { + // For the case of renaming an alias template, we actually rename the + // underlying alias declaration of the template. + if (const auto* TAT = dyn_cast<TypeAliasTemplateDecl>(Decl)) + Decl = TAT->getTemplatedDecl(); + + auto StartLoc = Decl->getLocation(); + auto EndLoc = StartLoc; + if (IsValidEditLoc(Context.getSourceManager(), StartLoc)) { + RenameInfo Info = {StartLoc, + EndLoc, + /*FromDecl=*/nullptr, + /*Context=*/nullptr, + /*Specifier=*/nullptr, + /*IgnorePrefixQualifers=*/true}; + RenameInfos.push_back(Info); + } + } + return true; + } + + bool VisitMemberExpr(const MemberExpr *Expr) { + const NamedDecl *Decl = Expr->getFoundDecl(); + auto StartLoc = Expr->getMemberLoc(); + auto EndLoc = Expr->getMemberLoc(); + if (isInUSRSet(Decl)) { + RenameInfos.push_back({StartLoc, EndLoc, + /*FromDecl=*/nullptr, + /*Context=*/nullptr, + /*Specifier=*/nullptr, + /*IgnorePrefixQualifiers=*/true}); + } + return true; + } + + bool VisitCXXConstructorDecl(const CXXConstructorDecl *CD) { + // Fix the constructor initializer when renaming class members. + for (const auto *Initializer : CD->inits()) { + // Ignore implicit initializers. + if (!Initializer->isWritten()) + continue; + + if (const FieldDecl *FD = Initializer->getMember()) { + if (isInUSRSet(FD)) { + auto Loc = Initializer->getSourceLocation(); + RenameInfos.push_back({Loc, Loc, + /*FromDecl=*/nullptr, + /*Context=*/nullptr, + /*Specifier=*/nullptr, + /*IgnorePrefixQualifiers=*/true}); + } + } + } + return true; + } + + bool VisitDeclRefExpr(const DeclRefExpr *Expr) { + const NamedDecl *Decl = Expr->getFoundDecl(); + // Get the underlying declaration of the shadow declaration introduced by a + // using declaration. + if (auto *UsingShadow = llvm::dyn_cast<UsingShadowDecl>(Decl)) { + Decl = UsingShadow->getTargetDecl(); + } + + auto StartLoc = Expr->getBeginLoc(); + // For template function call expressions like `foo<int>()`, we want to + // restrict the end of location to just before the `<` character. + SourceLocation EndLoc = Expr->hasExplicitTemplateArgs() + ? Expr->getLAngleLoc().getLocWithOffset(-1) + : Expr->getEndLoc(); + + if (const auto *MD = llvm::dyn_cast<CXXMethodDecl>(Decl)) { + if (isInUSRSet(MD)) { + // Handle renaming static template class methods, we only rename the + // name without prefix qualifiers and restrict the source range to the + // name. + RenameInfos.push_back({EndLoc, EndLoc, + /*FromDecl=*/nullptr, + /*Context=*/nullptr, + /*Specifier=*/nullptr, + /*IgnorePrefixQualifiers=*/true}); + return true; + } + } + + // In case of renaming an enum declaration, we have to explicitly handle + // unscoped enum constants referenced in expressions (e.g. + // "auto r = ns1::ns2::Green" where Green is an enum constant of an unscoped + // enum decl "ns1::ns2::Color") as these enum constants cannot be caught by + // TypeLoc. + if (const auto *T = llvm::dyn_cast<EnumConstantDecl>(Decl)) { + // FIXME: Handle the enum constant without prefix qualifiers (`a = Green`) + // when renaming an unscoped enum declaration with a new namespace. + if (!Expr->hasQualifier()) + return true; + + if (const auto *ED = + llvm::dyn_cast_or_null<EnumDecl>(getClosestAncestorDecl(*T))) { + if (ED->isScoped()) + return true; + Decl = ED; + } + // The current fix would qualify "ns1::ns2::Green" as + // "ns1::ns2::Color::Green". + // + // Get the EndLoc of the replacement by moving 1 character backward ( + // to exclude the last '::'). + // + // ns1::ns2::Green; + // ^ ^^ + // BeginLoc |EndLoc of the qualifier + // new EndLoc + EndLoc = Expr->getQualifierLoc().getEndLoc().getLocWithOffset(-1); + assert(EndLoc.isValid() && + "The enum constant should have prefix qualifers."); + } + if (isInUSRSet(Decl) && + IsValidEditLoc(Context.getSourceManager(), StartLoc)) { + RenameInfo Info = {StartLoc, + EndLoc, + Decl, + getClosestAncestorDecl(*Expr), + Expr->getQualifier(), + /*IgnorePrefixQualifers=*/false}; + RenameInfos.push_back(Info); + } + + return true; + } + + bool VisitUsingDecl(const UsingDecl *Using) { + for (const auto *UsingShadow : Using->shadows()) { + if (isInUSRSet(UsingShadow->getTargetDecl())) { + UsingDecls.push_back(Using); + break; + } + } + return true; + } + + bool VisitNestedNameSpecifierLocations(NestedNameSpecifierLoc NestedLoc) { + if (!NestedLoc.getNestedNameSpecifier()->getAsType()) + return true; + + if (const auto *TargetDecl = + getSupportedDeclFromTypeLoc(NestedLoc.getTypeLoc())) { + if (isInUSRSet(TargetDecl)) { + RenameInfo Info = {NestedLoc.getBeginLoc(), + EndLocationForType(NestedLoc.getTypeLoc()), + TargetDecl, + getClosestAncestorDecl(NestedLoc), + NestedLoc.getNestedNameSpecifier()->getPrefix(), + /*IgnorePrefixQualifers=*/false}; + RenameInfos.push_back(Info); + } + } + return true; + } + + bool VisitTypeLoc(TypeLoc Loc) { + auto Parents = Context.getParents(Loc); + TypeLoc ParentTypeLoc; + if (!Parents.empty()) { + // Handle cases of nested name specificier locations. + // + // The VisitNestedNameSpecifierLoc interface is not impelmented in + // RecursiveASTVisitor, we have to handle it explicitly. + if (const auto *NSL = Parents[0].get<NestedNameSpecifierLoc>()) { + VisitNestedNameSpecifierLocations(*NSL); + return true; + } + + if (const auto *TL = Parents[0].get<TypeLoc>()) + ParentTypeLoc = *TL; + } + + // Handle the outermost TypeLoc which is directly linked to the interesting + // declaration and don't handle nested name specifier locations. + if (const auto *TargetDecl = getSupportedDeclFromTypeLoc(Loc)) { + if (isInUSRSet(TargetDecl)) { + // Only handle the outermost typeLoc. + // + // For a type like "a::Foo", there will be two typeLocs for it. + // One ElaboratedType, the other is RecordType: + // + // ElaboratedType 0x33b9390 'a::Foo' sugar + // `-RecordType 0x338fef0 'class a::Foo' + // `-CXXRecord 0x338fe58 'Foo' + // + // Skip if this is an inner typeLoc. + if (!ParentTypeLoc.isNull() && + isInUSRSet(getSupportedDeclFromTypeLoc(ParentTypeLoc))) + return true; + + auto StartLoc = StartLocationForType(Loc); + auto EndLoc = EndLocationForType(Loc); + if (IsValidEditLoc(Context.getSourceManager(), StartLoc)) { + RenameInfo Info = {StartLoc, + EndLoc, + TargetDecl, + getClosestAncestorDecl(Loc), + GetNestedNameForType(Loc), + /*IgnorePrefixQualifers=*/false}; + RenameInfos.push_back(Info); + } + return true; + } + } + + // Handle specific template class specialiation cases. + if (const auto *TemplateSpecType = + dyn_cast<TemplateSpecializationType>(Loc.getType())) { + TypeLoc TargetLoc = Loc; + if (!ParentTypeLoc.isNull()) { + if (llvm::isa<ElaboratedType>(ParentTypeLoc.getType())) + TargetLoc = ParentTypeLoc; + } + + if (isInUSRSet(TemplateSpecType->getTemplateName().getAsTemplateDecl())) { + TypeLoc TargetLoc = Loc; + // FIXME: Find a better way to handle this case. + // For the qualified template class specification type like + // "ns::Foo<int>" in "ns::Foo<int>& f();", we want the parent typeLoc + // (ElaboratedType) of the TemplateSpecializationType in order to + // catch the prefix qualifiers "ns::". + if (!ParentTypeLoc.isNull() && + llvm::isa<ElaboratedType>(ParentTypeLoc.getType())) + TargetLoc = ParentTypeLoc; + + auto StartLoc = StartLocationForType(TargetLoc); + auto EndLoc = EndLocationForType(TargetLoc); + if (IsValidEditLoc(Context.getSourceManager(), StartLoc)) { + RenameInfo Info = { + StartLoc, + EndLoc, + TemplateSpecType->getTemplateName().getAsTemplateDecl(), + getClosestAncestorDecl( + ast_type_traits::DynTypedNode::create(TargetLoc)), + GetNestedNameForType(TargetLoc), + /*IgnorePrefixQualifers=*/false}; + RenameInfos.push_back(Info); + } + } + } + return true; + } + + // Returns a list of RenameInfo. + const std::vector<RenameInfo> &getRenameInfos() const { return RenameInfos; } + + // Returns a list of using declarations which are needed to update. + const std::vector<const UsingDecl *> &getUsingDecls() const { + return UsingDecls; + } + +private: + // Get the supported declaration from a given typeLoc. If the declaration type + // is not supported, returns nullptr. + const NamedDecl *getSupportedDeclFromTypeLoc(TypeLoc Loc) { + if (const auto* TT = Loc.getType()->getAs<clang::TypedefType>()) + return TT->getDecl(); + if (const auto *RD = Loc.getType()->getAsCXXRecordDecl()) + return RD; + if (const auto *ED = + llvm::dyn_cast_or_null<EnumDecl>(Loc.getType()->getAsTagDecl())) + return ED; + return nullptr; + } + + // Get the closest ancester which is a declaration of a given AST node. + template <typename ASTNodeType> + const Decl *getClosestAncestorDecl(const ASTNodeType &Node) { + auto Parents = Context.getParents(Node); + // FIXME: figure out how to handle it when there are multiple parents. + if (Parents.size() != 1) + return nullptr; + if (ast_type_traits::ASTNodeKind::getFromNodeKind<Decl>().isBaseOf( + Parents[0].getNodeKind())) + return Parents[0].template get<Decl>(); + return getClosestAncestorDecl(Parents[0]); + } + + // Get the parent typeLoc of a given typeLoc. If there is no such parent, + // return nullptr. + const TypeLoc *getParentTypeLoc(TypeLoc Loc) const { + auto Parents = Context.getParents(Loc); + // FIXME: figure out how to handle it when there are multiple parents. + if (Parents.size() != 1) + return nullptr; + return Parents[0].get<TypeLoc>(); + } + + // Check whether the USR of a given Decl is in the USRSet. + bool isInUSRSet(const Decl *Decl) const { + auto USR = getUSRForDecl(Decl); + if (USR.empty()) + return false; + return llvm::is_contained(USRSet, USR); + } + + const std::set<std::string> USRSet; + ASTContext &Context; + std::vector<RenameInfo> RenameInfos; + // Record all interested using declarations which contains the using-shadow + // declarations of the symbol declarations being renamed. + std::vector<const UsingDecl *> UsingDecls; +}; + +} // namespace + +SymbolOccurrences getOccurrencesOfUSRs(ArrayRef<std::string> USRs, + StringRef PrevName, Decl *Decl) { + USRLocFindingASTVisitor Visitor(USRs, PrevName, Decl->getASTContext()); + Visitor.TraverseDecl(Decl); + return Visitor.takeOccurrences(); +} + +std::vector<tooling::AtomicChange> +createRenameAtomicChanges(llvm::ArrayRef<std::string> USRs, + llvm::StringRef NewName, Decl *TranslationUnitDecl) { + RenameLocFinder Finder(USRs, TranslationUnitDecl->getASTContext()); + Finder.TraverseDecl(TranslationUnitDecl); + + const SourceManager &SM = + TranslationUnitDecl->getASTContext().getSourceManager(); + + std::vector<tooling::AtomicChange> AtomicChanges; + auto Replace = [&](SourceLocation Start, SourceLocation End, + llvm::StringRef Text) { + tooling::AtomicChange ReplaceChange = tooling::AtomicChange(SM, Start); + llvm::Error Err = ReplaceChange.replace( + SM, CharSourceRange::getTokenRange(Start, End), Text); + if (Err) { + llvm::errs() << "Failed to add replacement to AtomicChange: " + << llvm::toString(std::move(Err)) << "\n"; + return; + } + AtomicChanges.push_back(std::move(ReplaceChange)); + }; + + for (const auto &RenameInfo : Finder.getRenameInfos()) { + std::string ReplacedName = NewName.str(); + if (RenameInfo.IgnorePrefixQualifers) { + // Get the name without prefix qualifiers from NewName. + size_t LastColonPos = NewName.find_last_of(':'); + if (LastColonPos != std::string::npos) + ReplacedName = NewName.substr(LastColonPos + 1); + } else { + if (RenameInfo.FromDecl && RenameInfo.Context) { + if (!llvm::isa<clang::TranslationUnitDecl>( + RenameInfo.Context->getDeclContext())) { + ReplacedName = tooling::replaceNestedName( + RenameInfo.Specifier, RenameInfo.Context->getDeclContext(), + RenameInfo.FromDecl, + NewName.startswith("::") ? NewName.str() + : ("::" + NewName).str()); + } else { + // This fixes the case where type `T` is a parameter inside a function + // type (e.g. `std::function<void(T)>`) and the DeclContext of `T` + // becomes the translation unit. As a workaround, we simply use + // fully-qualified name here for all references whose `DeclContext` is + // the translation unit and ignore the possible existence of + // using-decls (in the global scope) that can shorten the replaced + // name. + llvm::StringRef ActualName = Lexer::getSourceText( + CharSourceRange::getTokenRange( + SourceRange(RenameInfo.Begin, RenameInfo.End)), + SM, TranslationUnitDecl->getASTContext().getLangOpts()); + // Add the leading "::" back if the name written in the code contains + // it. + if (ActualName.startswith("::") && !NewName.startswith("::")) { + ReplacedName = "::" + NewName.str(); + } + } + } + // If the NewName contains leading "::", add it back. + if (NewName.startswith("::") && NewName.substr(2) == ReplacedName) + ReplacedName = NewName.str(); + } + Replace(RenameInfo.Begin, RenameInfo.End, ReplacedName); + } + + // Hanlde using declarations explicitly as "using a::Foo" don't trigger + // typeLoc for "a::Foo". + for (const auto *Using : Finder.getUsingDecls()) + Replace(Using->getBeginLoc(), Using->getEndLoc(), "using " + NewName.str()); + + return AtomicChanges; +} + +} // end namespace tooling +} // end namespace clang diff --git a/contrib/llvm/tools/clang/lib/Tooling/RefactoringCallbacks.cpp b/contrib/llvm/tools/clang/lib/Tooling/RefactoringCallbacks.cpp new file mode 100644 index 000000000000..9fd333ca554e --- /dev/null +++ b/contrib/llvm/tools/clang/lib/Tooling/RefactoringCallbacks.cpp @@ -0,0 +1,241 @@ +//===--- RefactoringCallbacks.cpp - Structural query framework ------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// +//===----------------------------------------------------------------------===// +#include "clang/Tooling/RefactoringCallbacks.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" +#include "clang/Basic/SourceLocation.h" +#include "clang/Lex/Lexer.h" + +using llvm::StringError; +using llvm::make_error; + +namespace clang { +namespace tooling { + +RefactoringCallback::RefactoringCallback() {} +tooling::Replacements &RefactoringCallback::getReplacements() { + return Replace; +} + +ASTMatchRefactorer::ASTMatchRefactorer( + std::map<std::string, Replacements> &FileToReplaces) + : FileToReplaces(FileToReplaces) {} + +void ASTMatchRefactorer::addDynamicMatcher( + const ast_matchers::internal::DynTypedMatcher &Matcher, + RefactoringCallback *Callback) { + MatchFinder.addDynamicMatcher(Matcher, Callback); + Callbacks.push_back(Callback); +} + +class RefactoringASTConsumer : public ASTConsumer { +public: + explicit RefactoringASTConsumer(ASTMatchRefactorer &Refactoring) + : Refactoring(Refactoring) {} + + void HandleTranslationUnit(ASTContext &Context) override { + // The ASTMatchRefactorer is re-used between translation units. + // Clear the matchers so that each Replacement is only emitted once. + for (const auto &Callback : Refactoring.Callbacks) { + Callback->getReplacements().clear(); + } + Refactoring.MatchFinder.matchAST(Context); + for (const auto &Callback : Refactoring.Callbacks) { + for (const auto &Replacement : Callback->getReplacements()) { + llvm::Error Err = + Refactoring.FileToReplaces[Replacement.getFilePath()].add( + Replacement); + if (Err) { + llvm::errs() << "Skipping replacement " << Replacement.toString() + << " due to this error:\n" + << toString(std::move(Err)) << "\n"; + } + } + } + } + +private: + ASTMatchRefactorer &Refactoring; +}; + +std::unique_ptr<ASTConsumer> ASTMatchRefactorer::newASTConsumer() { + return llvm::make_unique<RefactoringASTConsumer>(*this); +} + +static Replacement replaceStmtWithText(SourceManager &Sources, const Stmt &From, + StringRef Text) { + return tooling::Replacement( + Sources, CharSourceRange::getTokenRange(From.getSourceRange()), Text); +} +static Replacement replaceStmtWithStmt(SourceManager &Sources, const Stmt &From, + const Stmt &To) { + return replaceStmtWithText( + Sources, From, + Lexer::getSourceText(CharSourceRange::getTokenRange(To.getSourceRange()), + Sources, LangOptions())); +} + +ReplaceStmtWithText::ReplaceStmtWithText(StringRef FromId, StringRef ToText) + : FromId(FromId), ToText(ToText) {} + +void ReplaceStmtWithText::run( + const ast_matchers::MatchFinder::MatchResult &Result) { + if (const Stmt *FromMatch = Result.Nodes.getNodeAs<Stmt>(FromId)) { + auto Err = Replace.add(tooling::Replacement( + *Result.SourceManager, + 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); + } + } +} + +ReplaceStmtWithStmt::ReplaceStmtWithStmt(StringRef FromId, StringRef ToId) + : FromId(FromId), ToId(ToId) {} + +void ReplaceStmtWithStmt::run( + const ast_matchers::MatchFinder::MatchResult &Result) { + 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, + bool PickTrueBranch) + : Id(Id), PickTrueBranch(PickTrueBranch) {} + +void ReplaceIfStmtWithItsBody::run( + const ast_matchers::MatchFinder::MatchResult &Result) { + if (const IfStmt *Node = Result.Nodes.getNodeAs<IfStmt>(Id)) { + const Stmt *Body = PickTrueBranch ? Node->getThen() : Node->getElse(); + if (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'. + 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); + } + } + } +} + +ReplaceNodeWithTemplate::ReplaceNodeWithTemplate( + llvm::StringRef FromId, std::vector<TemplateElement> Template) + : FromId(FromId), Template(std::move(Template)) {} + +llvm::Expected<std::unique_ptr<ReplaceNodeWithTemplate>> +ReplaceNodeWithTemplate::create(StringRef FromId, StringRef ToTemplate) { + std::vector<TemplateElement> ParsedTemplate; + for (size_t Index = 0; Index < ToTemplate.size();) { + if (ToTemplate[Index] == '$') { + if (ToTemplate.substr(Index, 2) == "$$") { + Index += 2; + ParsedTemplate.push_back( + TemplateElement{TemplateElement::Literal, "$"}); + } else if (ToTemplate.substr(Index, 2) == "${") { + size_t EndOfIdentifier = ToTemplate.find("}", Index); + if (EndOfIdentifier == std::string::npos) { + return make_error<StringError>( + "Unterminated ${...} in replacement template near " + + ToTemplate.substr(Index), + llvm::inconvertibleErrorCode()); + } + std::string SourceNodeName = + ToTemplate.substr(Index + 2, EndOfIdentifier - Index - 2); + ParsedTemplate.push_back( + TemplateElement{TemplateElement::Identifier, SourceNodeName}); + Index = EndOfIdentifier + 1; + } else { + return make_error<StringError>( + "Invalid $ in replacement template near " + + ToTemplate.substr(Index), + llvm::inconvertibleErrorCode()); + } + } else { + size_t NextIndex = ToTemplate.find('$', Index + 1); + ParsedTemplate.push_back( + TemplateElement{TemplateElement::Literal, + ToTemplate.substr(Index, NextIndex - Index)}); + Index = NextIndex; + } + } + return std::unique_ptr<ReplaceNodeWithTemplate>( + new ReplaceNodeWithTemplate(FromId, std::move(ParsedTemplate))); +} + +void ReplaceNodeWithTemplate::run( + const ast_matchers::MatchFinder::MatchResult &Result) { + const auto &NodeMap = Result.Nodes.getMap(); + + std::string ToText; + for (const auto &Element : Template) { + switch (Element.Type) { + case TemplateElement::Literal: + ToText += Element.Value; + break; + case TemplateElement::Identifier: { + auto NodeIter = NodeMap.find(Element.Value); + if (NodeIter == NodeMap.end()) { + llvm::errs() << "Node " << Element.Value + << " used in replacement template not bound in Matcher \n"; + llvm::report_fatal_error("Unbound node in replacement template."); + } + CharSourceRange Source = + CharSourceRange::getTokenRange(NodeIter->second.getSourceRange()); + ToText += Lexer::getSourceText(Source, *Result.SourceManager, + Result.Context->getLangOpts()); + break; + } + } + } + if (NodeMap.count(FromId) == 0) { + llvm::errs() << "Node to be replaced " << FromId + << " not bound in query.\n"; + llvm::report_fatal_error("FromId node not bound in MatchResult"); + } + auto Replacement = + tooling::Replacement(*Result.SourceManager, &NodeMap.at(FromId), ToText, + Result.Context->getLangOpts()); + llvm::Error Err = Replace.add(Replacement); + if (Err) { + llvm::errs() << "Query and replace failed in " << Replacement.getFilePath() + << "! " << llvm::toString(std::move(Err)) << "\n"; + llvm::report_fatal_error("Replacement failed"); + } +} + +} // end namespace tooling +} // end namespace clang diff --git a/contrib/llvm/tools/clang/lib/Tooling/StandaloneExecution.cpp b/contrib/llvm/tools/clang/lib/Tooling/StandaloneExecution.cpp new file mode 100644 index 000000000000..1daf792fb86f --- /dev/null +++ b/contrib/llvm/tools/clang/lib/Tooling/StandaloneExecution.cpp @@ -0,0 +1,93 @@ +//===- lib/Tooling/Execution.cpp - Standalone clang action execution. -----===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "clang/Tooling/StandaloneExecution.h" +#include "clang/Tooling/ToolExecutorPluginRegistry.h" + +namespace clang { +namespace tooling { + +static llvm::Error make_string_error(const llvm::Twine &Message) { + return llvm::make_error<llvm::StringError>(Message, + llvm::inconvertibleErrorCode()); +} + +const char *StandaloneToolExecutor::ExecutorName = "StandaloneToolExecutor"; + +static ArgumentsAdjuster getDefaultArgumentsAdjusters() { + return combineAdjusters( + getClangStripOutputAdjuster(), + combineAdjusters(getClangSyntaxOnlyAdjuster(), + getClangStripDependencyFileAdjuster())); +} + +StandaloneToolExecutor::StandaloneToolExecutor( + const CompilationDatabase &Compilations, + llvm::ArrayRef<std::string> SourcePaths, + IntrusiveRefCntPtr<llvm::vfs::FileSystem> BaseFS, + std::shared_ptr<PCHContainerOperations> PCHContainerOps) + : Tool(Compilations, SourcePaths, std::move(PCHContainerOps), + std::move(BaseFS)), + Context(&Results), ArgsAdjuster(getDefaultArgumentsAdjusters()) { + // Use self-defined default argument adjusters instead of the default + // adjusters that come with the old `ClangTool`. + Tool.clearArgumentsAdjusters(); +} + +StandaloneToolExecutor::StandaloneToolExecutor( + CommonOptionsParser Options, + std::shared_ptr<PCHContainerOperations> PCHContainerOps) + : OptionsParser(std::move(Options)), + Tool(OptionsParser->getCompilations(), OptionsParser->getSourcePathList(), + std::move(PCHContainerOps)), + Context(&Results), ArgsAdjuster(getDefaultArgumentsAdjusters()) { + Tool.clearArgumentsAdjusters(); +} + +llvm::Error StandaloneToolExecutor::execute( + llvm::ArrayRef< + std::pair<std::unique_ptr<FrontendActionFactory>, ArgumentsAdjuster>> + Actions) { + if (Actions.empty()) + return make_string_error("No action to execute."); + + if (Actions.size() != 1) + return make_string_error( + "Only support executing exactly 1 action at this point."); + + auto &Action = Actions.front(); + Tool.appendArgumentsAdjuster(Action.second); + Tool.appendArgumentsAdjuster(ArgsAdjuster); + if (Tool.run(Action.first.get())) + return make_string_error("Failed to run action."); + + return llvm::Error::success(); +} + +class StandaloneToolExecutorPlugin : public ToolExecutorPlugin { +public: + llvm::Expected<std::unique_ptr<ToolExecutor>> + create(CommonOptionsParser &OptionsParser) override { + if (OptionsParser.getSourcePathList().empty()) + return make_string_error( + "[StandaloneToolExecutorPlugin] No positional argument found."); + return llvm::make_unique<StandaloneToolExecutor>(std::move(OptionsParser)); + } +}; + +static ToolExecutorPluginRegistry::Add<StandaloneToolExecutorPlugin> + X("standalone", "Runs FrontendActions on a set of files provided " + "via positional arguments."); + +// This anchor is used to force the linker to link in the generated object file +// and thus register the plugin. +volatile int StandaloneToolExecutorAnchorSource = 0; + +} // end namespace tooling +} // end namespace clang diff --git a/contrib/llvm/tools/clang/lib/Tooling/Tooling.cpp b/contrib/llvm/tools/clang/lib/Tooling/Tooling.cpp new file mode 100644 index 000000000000..63aa64a5330d --- /dev/null +++ b/contrib/llvm/tools/clang/lib/Tooling/Tooling.cpp @@ -0,0 +1,611 @@ +//===- Tooling.cpp - Running clang standalone tools -----------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// This file implements functions to run clang tools standalone instead +// of running them as a plugin. +// +//===----------------------------------------------------------------------===// + +#include "clang/Tooling/Tooling.h" +#include "clang/Basic/Diagnostic.h" +#include "clang/Basic/DiagnosticIDs.h" +#include "clang/Basic/DiagnosticOptions.h" +#include "clang/Basic/FileManager.h" +#include "clang/Basic/FileSystemOptions.h" +#include "clang/Basic/LLVM.h" +#include "clang/Driver/Compilation.h" +#include "clang/Driver/Driver.h" +#include "clang/Driver/Job.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/CompilerInvocation.h" +#include "clang/Frontend/FrontendDiagnostic.h" +#include "clang/Frontend/FrontendOptions.h" +#include "clang/Frontend/TextDiagnosticPrinter.h" +#include "clang/Lex/HeaderSearchOptions.h" +#include "clang/Lex/PreprocessorOptions.h" +#include "clang/Tooling/ArgumentsAdjusters.h" +#include "clang/Tooling/CompilationDatabase.h" +#include "llvm/ADT/ArrayRef.h" +#include "llvm/ADT/IntrusiveRefCntPtr.h" +#include "llvm/ADT/SmallString.h" +#include "llvm/ADT/StringRef.h" +#include "llvm/ADT/Twine.h" +#include "llvm/Option/ArgList.h" +#include "llvm/Option/OptTable.h" +#include "llvm/Option/Option.h" +#include "llvm/Support/Casting.h" +#include "llvm/Support/Debug.h" +#include "llvm/Support/ErrorHandling.h" +#include "llvm/Support/FileSystem.h" +#include "llvm/Support/Host.h" +#include "llvm/Support/MemoryBuffer.h" +#include "llvm/Support/Path.h" +#include "llvm/Support/VirtualFileSystem.h" +#include "llvm/Support/raw_ostream.h" +#include <cassert> +#include <cstring> +#include <memory> +#include <string> +#include <system_error> +#include <utility> +#include <vector> + +#define DEBUG_TYPE "clang-tooling" + +using namespace clang; +using namespace tooling; + +ToolAction::~ToolAction() = default; + +FrontendActionFactory::~FrontendActionFactory() = default; + +// FIXME: This file contains structural duplication with other parts of the +// code that sets up a compiler to run tools on it, and we should refactor +// it to be based on the same framework. + +/// Builds a clang driver initialized for running clang tools. +static driver::Driver * +newDriver(DiagnosticsEngine *Diagnostics, const char *BinaryName, + IntrusiveRefCntPtr<llvm::vfs::FileSystem> VFS) { + driver::Driver *CompilerDriver = + new driver::Driver(BinaryName, llvm::sys::getDefaultTargetTriple(), + *Diagnostics, std::move(VFS)); + CompilerDriver->setTitle("clang_based_tool"); + return CompilerDriver; +} + +/// Retrieves the clang CC1 specific flags out of the compilation's jobs. +/// +/// Returns nullptr on error. +static const llvm::opt::ArgStringList *getCC1Arguments( + DiagnosticsEngine *Diagnostics, driver::Compilation *Compilation) { + // We expect to get back exactly one Command job, if we didn't something + // failed. Extract that job from the Compilation. + const driver::JobList &Jobs = Compilation->getJobs(); + if (Jobs.size() != 1 || !isa<driver::Command>(*Jobs.begin())) { + SmallString<256> error_msg; + llvm::raw_svector_ostream error_stream(error_msg); + Jobs.Print(error_stream, "; ", true); + Diagnostics->Report(diag::err_fe_expected_compiler_job) + << error_stream.str(); + return nullptr; + } + + // The one job we find should be to invoke clang again. + const auto &Cmd = cast<driver::Command>(*Jobs.begin()); + if (StringRef(Cmd.getCreator().getName()) != "clang") { + Diagnostics->Report(diag::err_fe_expected_clang_command); + return nullptr; + } + + return &Cmd.getArguments(); +} + +namespace clang { +namespace tooling { + +/// Returns a clang build invocation initialized from the CC1 flags. +CompilerInvocation *newInvocation( + DiagnosticsEngine *Diagnostics, const llvm::opt::ArgStringList &CC1Args) { + assert(!CC1Args.empty() && "Must at least contain the program name!"); + CompilerInvocation *Invocation = new CompilerInvocation; + CompilerInvocation::CreateFromArgs( + *Invocation, CC1Args.data() + 1, CC1Args.data() + CC1Args.size(), + *Diagnostics); + Invocation->getFrontendOpts().DisableFree = false; + Invocation->getCodeGenOpts().DisableFree = false; + return Invocation; +} + +bool runToolOnCode(FrontendAction *ToolAction, const Twine &Code, + const Twine &FileName, + std::shared_ptr<PCHContainerOperations> PCHContainerOps) { + return runToolOnCodeWithArgs(ToolAction, Code, std::vector<std::string>(), + FileName, "clang-tool", + std::move(PCHContainerOps)); +} + +} // namespace tooling +} // namespace clang + +static std::vector<std::string> +getSyntaxOnlyToolArgs(const Twine &ToolName, + const std::vector<std::string> &ExtraArgs, + StringRef FileName) { + std::vector<std::string> Args; + Args.push_back(ToolName.str()); + Args.push_back("-fsyntax-only"); + Args.insert(Args.end(), ExtraArgs.begin(), ExtraArgs.end()); + Args.push_back(FileName.str()); + return Args; +} + +namespace clang { +namespace tooling { + +bool runToolOnCodeWithArgs( + FrontendAction *ToolAction, const Twine &Code, + llvm::IntrusiveRefCntPtr<llvm::vfs::FileSystem> VFS, + const std::vector<std::string> &Args, const Twine &FileName, + const Twine &ToolName, + std::shared_ptr<PCHContainerOperations> PCHContainerOps) { + SmallString<16> FileNameStorage; + StringRef FileNameRef = FileName.toNullTerminatedStringRef(FileNameStorage); + + llvm::IntrusiveRefCntPtr<FileManager> Files( + new FileManager(FileSystemOptions(), VFS)); + ArgumentsAdjuster Adjuster = getClangStripDependencyFileAdjuster(); + ToolInvocation Invocation( + getSyntaxOnlyToolArgs(ToolName, Adjuster(Args, FileNameRef), FileNameRef), + ToolAction, Files.get(), + std::move(PCHContainerOps)); + return Invocation.run(); +} + +bool runToolOnCodeWithArgs( + FrontendAction *ToolAction, const Twine &Code, + const std::vector<std::string> &Args, const Twine &FileName, + const Twine &ToolName, + std::shared_ptr<PCHContainerOperations> PCHContainerOps, + const FileContentMappings &VirtualMappedFiles) { + llvm::IntrusiveRefCntPtr<llvm::vfs::OverlayFileSystem> OverlayFileSystem( + new llvm::vfs::OverlayFileSystem(llvm::vfs::getRealFileSystem())); + llvm::IntrusiveRefCntPtr<llvm::vfs::InMemoryFileSystem> InMemoryFileSystem( + new llvm::vfs::InMemoryFileSystem); + OverlayFileSystem->pushOverlay(InMemoryFileSystem); + + SmallString<1024> CodeStorage; + InMemoryFileSystem->addFile(FileName, 0, + llvm::MemoryBuffer::getMemBuffer( + Code.toNullTerminatedStringRef(CodeStorage))); + + for (auto &FilenameWithContent : VirtualMappedFiles) { + InMemoryFileSystem->addFile( + FilenameWithContent.first, 0, + llvm::MemoryBuffer::getMemBuffer(FilenameWithContent.second)); + } + + return runToolOnCodeWithArgs(ToolAction, Code, OverlayFileSystem, Args, + FileName, ToolName); +} + +llvm::Expected<std::string> getAbsolutePath(llvm::vfs::FileSystem &FS, + StringRef File) { + StringRef RelativePath(File); + // FIXME: Should '.\\' be accepted on Win32? + if (RelativePath.startswith("./")) { + RelativePath = RelativePath.substr(strlen("./")); + } + + SmallString<1024> AbsolutePath = RelativePath; + if (auto EC = FS.makeAbsolute(AbsolutePath)) + return llvm::errorCodeToError(EC); + llvm::sys::path::native(AbsolutePath); + return AbsolutePath.str(); +} + +std::string getAbsolutePath(StringRef File) { + return llvm::cantFail(getAbsolutePath(*llvm::vfs::getRealFileSystem(), File)); +} + +void addTargetAndModeForProgramName(std::vector<std::string> &CommandLine, + StringRef InvokedAs) { + if (!CommandLine.empty() && !InvokedAs.empty()) { + bool AlreadyHasTarget = false; + bool AlreadyHasMode = false; + // Skip CommandLine[0]. + for (auto Token = ++CommandLine.begin(); Token != CommandLine.end(); + ++Token) { + StringRef TokenRef(*Token); + AlreadyHasTarget |= + (TokenRef == "-target" || TokenRef.startswith("-target=")); + AlreadyHasMode |= (TokenRef == "--driver-mode" || + TokenRef.startswith("--driver-mode=")); + } + auto TargetMode = + driver::ToolChain::getTargetAndModeFromProgramName(InvokedAs); + if (!AlreadyHasMode && TargetMode.DriverMode) { + CommandLine.insert(++CommandLine.begin(), TargetMode.DriverMode); + } + if (!AlreadyHasTarget && TargetMode.TargetIsValid) { + CommandLine.insert(++CommandLine.begin(), {"-target", + TargetMode.TargetPrefix}); + } + } +} + +} // namespace tooling +} // namespace clang + +namespace { + +class SingleFrontendActionFactory : public FrontendActionFactory { + FrontendAction *Action; + +public: + SingleFrontendActionFactory(FrontendAction *Action) : Action(Action) {} + + FrontendAction *create() override { return Action; } +}; + +} // namespace + +ToolInvocation::ToolInvocation( + std::vector<std::string> CommandLine, ToolAction *Action, + FileManager *Files, std::shared_ptr<PCHContainerOperations> PCHContainerOps) + : CommandLine(std::move(CommandLine)), Action(Action), OwnsAction(false), + Files(Files), PCHContainerOps(std::move(PCHContainerOps)) {} + +ToolInvocation::ToolInvocation( + std::vector<std::string> CommandLine, FrontendAction *FAction, + FileManager *Files, std::shared_ptr<PCHContainerOperations> PCHContainerOps) + : CommandLine(std::move(CommandLine)), + Action(new SingleFrontendActionFactory(FAction)), OwnsAction(true), + Files(Files), PCHContainerOps(std::move(PCHContainerOps)) {} + +ToolInvocation::~ToolInvocation() { + if (OwnsAction) + delete Action; +} + +void ToolInvocation::mapVirtualFile(StringRef FilePath, StringRef Content) { + SmallString<1024> PathStorage; + llvm::sys::path::native(FilePath, PathStorage); + MappedFileContents[PathStorage] = Content; +} + +bool ToolInvocation::run() { + std::vector<const char*> Argv; + for (const std::string &Str : CommandLine) + 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( + IntrusiveRefCntPtr<DiagnosticIDs>(new DiagnosticIDs()), &*DiagOpts, + DiagConsumer ? DiagConsumer : &DiagnosticPrinter, false); + + const std::unique_ptr<driver::Driver> Driver( + newDriver(&Diagnostics, BinaryName, Files->getVirtualFileSystem())); + // The "input file not found" diagnostics from the driver are useful. + // The driver is only aware of the VFS working directory, but some clients + // change this at the FileManager level instead. + // In this case the checks have false positives, so skip them. + if (!Files->getFileSystemOpts().WorkingDir.empty()) + Driver->setCheckInputsExist(false); + const std::unique_ptr<driver::Compilation> Compilation( + Driver->BuildCompilation(llvm::makeArrayRef(Argv))); + if (!Compilation) + return false; + const llvm::opt::ArgStringList *const CC1Args = getCC1Arguments( + &Diagnostics, Compilation.get()); + if (!CC1Args) + return false; + std::unique_ptr<CompilerInvocation> Invocation( + newInvocation(&Diagnostics, *CC1Args)); + // FIXME: remove this when all users have migrated! + for (const auto &It : MappedFileContents) { + // Inject the code as the given file name into the preprocessor options. + std::unique_ptr<llvm::MemoryBuffer> Input = + llvm::MemoryBuffer::getMemBuffer(It.getValue()); + Invocation->getPreprocessorOpts().addRemappedFile(It.getKey(), + Input.release()); + } + return runInvocation(BinaryName, Compilation.get(), std::move(Invocation), + std::move(PCHContainerOps)); +} + +bool ToolInvocation::runInvocation( + const char *BinaryName, driver::Compilation *Compilation, + std::shared_ptr<CompilerInvocation> Invocation, + std::shared_ptr<PCHContainerOperations> PCHContainerOps) { + // Show the invocation, with -v. + if (Invocation->getHeaderSearchOpts().Verbose) { + llvm::errs() << "clang Invocation:\n"; + Compilation->getJobs().Print(llvm::errs(), "\n", true); + llvm::errs() << "\n"; + } + + return Action->runInvocation(std::move(Invocation), Files, + std::move(PCHContainerOps), DiagConsumer); +} + +bool FrontendActionFactory::runInvocation( + std::shared_ptr<CompilerInvocation> Invocation, FileManager *Files, + std::shared_ptr<PCHContainerOperations> PCHContainerOps, + DiagnosticConsumer *DiagConsumer) { + // Create a compiler instance to handle the actual work. + CompilerInstance Compiler(std::move(PCHContainerOps)); + Compiler.setInvocation(std::move(Invocation)); + Compiler.setFileManager(Files); + + // The FrontendAction can have lifetime requirements for Compiler or its + // members, and we need to ensure it's deleted earlier than Compiler. So we + // pass it to an std::unique_ptr declared after the Compiler variable. + std::unique_ptr<FrontendAction> ScopedToolAction(create()); + + // Create the compiler's actual diagnostics engine. + Compiler.createDiagnostics(DiagConsumer, /*ShouldOwnClient=*/false); + if (!Compiler.hasDiagnostics()) + return false; + + Compiler.createSourceManager(*Files); + + const bool Success = Compiler.ExecuteAction(*ScopedToolAction); + + Files->clearStatCache(); + return Success; +} + +ClangTool::ClangTool(const CompilationDatabase &Compilations, + ArrayRef<std::string> SourcePaths, + std::shared_ptr<PCHContainerOperations> PCHContainerOps, + IntrusiveRefCntPtr<llvm::vfs::FileSystem> BaseFS) + : Compilations(Compilations), SourcePaths(SourcePaths), + PCHContainerOps(std::move(PCHContainerOps)), + OverlayFileSystem(new llvm::vfs::OverlayFileSystem(std::move(BaseFS))), + InMemoryFileSystem(new llvm::vfs::InMemoryFileSystem), + Files(new FileManager(FileSystemOptions(), OverlayFileSystem)) { + OverlayFileSystem->pushOverlay(InMemoryFileSystem); + appendArgumentsAdjuster(getClangStripOutputAdjuster()); + appendArgumentsAdjuster(getClangSyntaxOnlyAdjuster()); + appendArgumentsAdjuster(getClangStripDependencyFileAdjuster()); +} + +ClangTool::~ClangTool() = default; + +void ClangTool::mapVirtualFile(StringRef FilePath, StringRef Content) { + MappedFileContents.push_back(std::make_pair(FilePath, Content)); +} + +void ClangTool::appendArgumentsAdjuster(ArgumentsAdjuster Adjuster) { + ArgsAdjuster = combineAdjusters(std::move(ArgsAdjuster), std::move(Adjuster)); +} + +void ClangTool::clearArgumentsAdjusters() { + ArgsAdjuster = nullptr; +} + +static void injectResourceDir(CommandLineArguments &Args, const char *Argv0, + void *MainAddr) { + // Allow users to override the resource dir. + for (StringRef Arg : Args) + if (Arg.startswith("-resource-dir")) + return; + + // If there's no override in place add our resource dir. + Args.push_back("-resource-dir=" + + CompilerInvocation::GetResourcesPath(Argv0, MainAddr)); +} + +int ClangTool::run(ToolAction *Action) { + // Exists solely for the purpose of lookup of the resource path. + // This just needs to be some symbol in the binary. + static int StaticSymbol; + + // First insert all absolute paths into the in-memory VFS. These are global + // for all compile commands. + if (SeenWorkingDirectories.insert("/").second) + for (const auto &MappedFile : MappedFileContents) + if (llvm::sys::path::is_absolute(MappedFile.first)) + InMemoryFileSystem->addFile( + MappedFile.first, 0, + llvm::MemoryBuffer::getMemBuffer(MappedFile.second)); + + bool ProcessingFailed = false; + bool FileSkipped = false; + // Compute all absolute paths before we run any actions, as those will change + // the working directory. + std::vector<std::string> AbsolutePaths; + AbsolutePaths.reserve(SourcePaths.size()); + for (const auto &SourcePath : SourcePaths) { + auto AbsPath = getAbsolutePath(*OverlayFileSystem, SourcePath); + if (!AbsPath) { + llvm::errs() << "Skipping " << SourcePath + << ". Error while getting an absolute path: " + << llvm::toString(AbsPath.takeError()) << "\n"; + continue; + } + AbsolutePaths.push_back(std::move(*AbsPath)); + } + + // Remember the working directory in case we need to restore it. + std::string InitialWorkingDir; + if (RestoreCWD) { + if (auto CWD = OverlayFileSystem->getCurrentWorkingDirectory()) { + InitialWorkingDir = std::move(*CWD); + } else { + llvm::errs() << "Could not get working directory: " + << CWD.getError().message() << "\n"; + } + } + + for (llvm::StringRef File : AbsolutePaths) { + // Currently implementations of CompilationDatabase::getCompileCommands can + // change the state of the file system (e.g. prepare generated headers), so + // this method needs to run right before we invoke the tool, as the next + // file may require a different (incompatible) state of the file system. + // + // FIXME: Make the compilation database interface more explicit about the + // requirements to the order of invocation of its members. + std::vector<CompileCommand> CompileCommandsForFile = + Compilations.getCompileCommands(File); + if (CompileCommandsForFile.empty()) { + llvm::errs() << "Skipping " << File << ". Compile command not found.\n"; + FileSkipped = true; + continue; + } + for (CompileCommand &CompileCommand : CompileCommandsForFile) { + // FIXME: chdir is thread hostile; on the other hand, creating the same + // behavior as chdir is complex: chdir resolves the path once, thus + // guaranteeing that all subsequent relative path operations work + // on the same path the original chdir resulted in. This makes a + // difference for example on network filesystems, where symlinks might be + // switched during runtime of the tool. Fixing this depends on having a + // file system abstraction that allows openat() style interactions. + if (OverlayFileSystem->setCurrentWorkingDirectory( + CompileCommand.Directory)) + llvm::report_fatal_error("Cannot chdir into \"" + + Twine(CompileCommand.Directory) + "\n!"); + + // Now fill the in-memory VFS with the relative file mappings so it will + // have the correct relative paths. We never remove mappings but that + // should be fine. + if (SeenWorkingDirectories.insert(CompileCommand.Directory).second) + for (const auto &MappedFile : MappedFileContents) + if (!llvm::sys::path::is_absolute(MappedFile.first)) + InMemoryFileSystem->addFile( + MappedFile.first, 0, + llvm::MemoryBuffer::getMemBuffer(MappedFile.second)); + + std::vector<std::string> CommandLine = CompileCommand.CommandLine; + if (ArgsAdjuster) + CommandLine = ArgsAdjuster(CommandLine, CompileCommand.Filename); + assert(!CommandLine.empty()); + + // Add the resource dir based on the binary of this tool. argv[0] in the + // compilation database may refer to a different compiler and we want to + // pick up the very same standard library that compiler is using. The + // builtin headers in the resource dir need to match the exact clang + // version the tool is using. + // FIXME: On linux, GetMainExecutable is independent of the value of the + // first argument, thus allowing ClangTool and runToolOnCode to just + // pass in made-up names here. Make sure this works on other platforms. + injectResourceDir(CommandLine, "clang_tool", &StaticSymbol); + + // FIXME: We need a callback mechanism for the tool writer to output a + // customized message for each file. + LLVM_DEBUG({ llvm::dbgs() << "Processing: " << File << ".\n"; }); + ToolInvocation Invocation(std::move(CommandLine), Action, Files.get(), + PCHContainerOps); + Invocation.setDiagnosticConsumer(DiagConsumer); + + if (!Invocation.run()) { + // FIXME: Diagnostics should be used instead. + llvm::errs() << "Error while processing " << File << ".\n"; + ProcessingFailed = true; + } + } + } + + if (!InitialWorkingDir.empty()) { + if (auto EC = + OverlayFileSystem->setCurrentWorkingDirectory(InitialWorkingDir)) + llvm::errs() << "Error when trying to restore working dir: " + << EC.message() << "\n"; + } + return ProcessingFailed ? 1 : (FileSkipped ? 2 : 0); +} + +namespace { + +class ASTBuilderAction : public ToolAction { + std::vector<std::unique_ptr<ASTUnit>> &ASTs; + +public: + ASTBuilderAction(std::vector<std::unique_ptr<ASTUnit>> &ASTs) : ASTs(ASTs) {} + + bool runInvocation(std::shared_ptr<CompilerInvocation> Invocation, + FileManager *Files, + std::shared_ptr<PCHContainerOperations> PCHContainerOps, + DiagnosticConsumer *DiagConsumer) override { + std::unique_ptr<ASTUnit> AST = ASTUnit::LoadFromCompilerInvocation( + Invocation, std::move(PCHContainerOps), + CompilerInstance::createDiagnostics(&Invocation->getDiagnosticOpts(), + DiagConsumer, + /*ShouldOwnClient=*/false), + Files); + if (!AST) + return false; + + ASTs.push_back(std::move(AST)); + return true; + } +}; + +} // namespace + +int ClangTool::buildASTs(std::vector<std::unique_ptr<ASTUnit>> &ASTs) { + ASTBuilderAction Action(ASTs); + return run(&Action); +} + +void ClangTool::setRestoreWorkingDir(bool RestoreCWD) { + this->RestoreCWD = RestoreCWD; +} + +namespace clang { +namespace tooling { + +std::unique_ptr<ASTUnit> +buildASTFromCode(StringRef Code, StringRef FileName, + std::shared_ptr<PCHContainerOperations> PCHContainerOps) { + return buildASTFromCodeWithArgs(Code, std::vector<std::string>(), FileName, + "clang-tool", std::move(PCHContainerOps)); +} + +std::unique_ptr<ASTUnit> buildASTFromCodeWithArgs( + StringRef Code, const std::vector<std::string> &Args, StringRef FileName, + StringRef ToolName, std::shared_ptr<PCHContainerOperations> PCHContainerOps, + ArgumentsAdjuster Adjuster) { + std::vector<std::unique_ptr<ASTUnit>> ASTs; + ASTBuilderAction Action(ASTs); + llvm::IntrusiveRefCntPtr<llvm::vfs::OverlayFileSystem> OverlayFileSystem( + new llvm::vfs::OverlayFileSystem(llvm::vfs::getRealFileSystem())); + llvm::IntrusiveRefCntPtr<llvm::vfs::InMemoryFileSystem> InMemoryFileSystem( + new llvm::vfs::InMemoryFileSystem); + OverlayFileSystem->pushOverlay(InMemoryFileSystem); + llvm::IntrusiveRefCntPtr<FileManager> Files( + new FileManager(FileSystemOptions(), OverlayFileSystem)); + + ToolInvocation Invocation( + getSyntaxOnlyToolArgs(ToolName, Adjuster(Args, FileName), FileName), + &Action, Files.get(), std::move(PCHContainerOps)); + + InMemoryFileSystem->addFile(FileName, 0, + llvm::MemoryBuffer::getMemBufferCopy(Code)); + if (!Invocation.run()) + return nullptr; + + assert(ASTs.size() == 1); + return std::move(ASTs[0]); +} + +} // namespace tooling +} // namespace clang |