[clang-tools-extra] r366698 - [clangd] Add dlog()s for SelectionTree, enabling -debug-only=SelectionTree.cpp
Sam McCall via cfe-commits
cfe-commits at lists.llvm.org
Mon Jul 22 08:55:53 PDT 2019
Author: sammccall
Date: Mon Jul 22 08:55:53 2019
New Revision: 366698
URL: http://llvm.org/viewvc/llvm-project?rev=366698&view=rev
Log:
[clangd] Add dlog()s for SelectionTree, enabling -debug-only=SelectionTree.cpp
Summary:
SelectionTree is a RecursiveASTVisitor which processes getSourceRange() for
every node. This is a lot of surface area with the AST, as getSourceRange()
is specialized for *many* node types.
And the resulting SelectionTree depends on the source ranges of many
visited nodes, and the order of traversal.
Put together, this means we really need a traversal log to debug when we
get an unexpected SelectionTree. I've built this ad-hoc a few times, now
it's time to check it in.
Example output:
```
D[14:07:44.184] Computing selection for </usr/local/google/home/sammccall/test.cc:1:7, col:8>
D[14:07:44.184] push: VarDecl const auto x = 42
D[14:07:44.184] claimRange: </usr/local/google/home/sammccall/test.cc:1:12, col:13>
D[14:07:44.184] push: NestedNameSpecifierLoc (empty NestedNameSpecifierLoc)
D[14:07:44.184] pop: NestedNameSpecifierLoc (empty NestedNameSpecifierLoc)
D[14:07:44.184] push: QualifiedTypeLoc const auto
D[14:07:44.184] pop: QualifiedTypeLoc const auto
D[14:07:44.184] claimRange: </usr/local/google/home/sammccall/test.cc:1:7, col:11>
D[14:07:44.184] hit selection: </usr/local/google/home/sammccall/test.cc:1:7, col:8>
D[14:07:44.184] skip: IntegerLiteral 42
D[14:07:44.184] skipped range = </usr/local/google/home/sammccall/test.cc:1:16>
D[14:07:44.184] pop: VarDecl const auto x = 42
D[14:07:44.184] claimRange: </usr/local/google/home/sammccall/test.cc:1:1, col:18>
D[14:07:44.184] skip: VarDecl int y = 43
D[14:07:44.184] skipped range = </usr/local/google/home/sammccall/test.cc:2:1, col:9>
D[14:07:44.184] Built selection tree
TranslationUnitDecl
VarDecl const auto x = 42
.QualifiedTypeLoc const auto
```
Reviewers: hokein
Subscribers: ilya-biryukov, MaskRay, jkorous, arphaman, kadircet, llvm-commits
Tags: #llvm
Differential Revision: https://reviews.llvm.org/D65073
Modified:
clang-tools-extra/trunk/clangd/Selection.cpp
clang-tools-extra/trunk/clangd/Selection.h
Modified: clang-tools-extra/trunk/clangd/Selection.cpp
URL: http://llvm.org/viewvc/llvm-project/clang-tools-extra/trunk/clangd/Selection.cpp?rev=366698&r1=366697&r2=366698&view=diff
==============================================================================
--- clang-tools-extra/trunk/clangd/Selection.cpp (original)
+++ clang-tools-extra/trunk/clangd/Selection.cpp Mon Jul 22 08:55:53 2019
@@ -8,15 +8,19 @@
#include "Selection.h"
#include "ClangdUnit.h"
+#include "Logger.h"
#include "SourceCode.h"
#include "clang/AST/ASTTypeTraits.h"
#include "clang/AST/PrettyPrinter.h"
#include "clang/AST/RecursiveASTVisitor.h"
#include "clang/AST/TypeLoc.h"
+#include "clang/Basic/SourceLocation.h"
#include "clang/Basic/SourceManager.h"
#include "clang/Basic/TokenKinds.h"
#include "llvm/ADT/STLExtras.h"
+#include "llvm/Support/raw_ostream.h"
#include <algorithm>
+#include <string>
namespace clang {
namespace clangd {
@@ -59,6 +63,31 @@ private:
std::vector<std::pair<unsigned, unsigned>> Ranges; // Always sorted.
};
+// Dump a node for debugging.
+// DynTypedNode::print() doesn't include the kind of node, which is useful.
+void printNode(llvm::raw_ostream &OS, const DynTypedNode &N,
+ const PrintingPolicy &PP) {
+ if (const TypeLoc *TL = N.get<TypeLoc>()) {
+ // TypeLoc is a hierarchy, but has only a single ASTNodeKind.
+ // Synthesize the name from the Type subclass (except for QualifiedTypeLoc).
+ if (TL->getTypeLocClass() == TypeLoc::Qualified)
+ OS << "QualifiedTypeLoc";
+ else
+ OS << TL->getType()->getTypeClassName() << "TypeLoc";
+ } else {
+ OS << N.getNodeKind().asStringRef();
+ }
+ OS << " ";
+ N.print(OS, PP);
+}
+
+std::string printNodeToString(const DynTypedNode &N, const PrintingPolicy &PP) {
+ std::string S;
+ llvm::raw_string_ostream OS(S);
+ printNode(OS, N, PP);
+ return std::move(OS.str());
+}
+
// We find the selection by visiting written nodes in the AST, looking for nodes
// that intersect with the selected character range.
//
@@ -71,9 +100,9 @@ class SelectionVisitor : public Recursiv
public:
// Runs the visitor to gather selected nodes and their ancestors.
// If there is any selection, the root (TUDecl) is the first node.
- static std::deque<Node> collect(ASTContext &AST, unsigned Begin,
- unsigned End, FileID File) {
- SelectionVisitor V(AST, Begin, End, File);
+ static std::deque<Node> collect(ASTContext &AST, const PrintingPolicy &PP,
+ unsigned Begin, unsigned End, FileID File) {
+ SelectionVisitor V(AST, PP, Begin, End, File);
V.TraverseAST(AST);
assert(V.Stack.size() == 1 && "Unpaired push/pop?");
assert(V.Stack.top() == &V.Nodes.front());
@@ -114,9 +143,12 @@ public:
}
// Stmt is the same, but this form allows the data recursion optimization.
bool dataTraverseStmtPre(Stmt *X) {
- if (!X || canSafelySkipNode(X->getSourceRange()))
+ if (!X)
+ return false;
+ auto N = DynTypedNode::create(*X);
+ if (canSafelySkipNode(N))
return false;
- push(DynTypedNode::create(*X));
+ push(std::move(N));
return true;
}
bool dataTraverseStmtPost(Stmt *X) {
@@ -130,10 +162,10 @@ public:
private:
using Base = RecursiveASTVisitor<SelectionVisitor>;
- SelectionVisitor(ASTContext &AST, unsigned SelBegin, unsigned SelEnd,
- FileID SelFile)
+ SelectionVisitor(ASTContext &AST, const PrintingPolicy &PP, unsigned SelBegin,
+ unsigned SelEnd, FileID SelFile)
: SM(AST.getSourceManager()), LangOpts(AST.getLangOpts()),
- SelBegin(SelBegin), SelEnd(SelEnd), SelFile(SelFile),
+ PrintPolicy(PP), SelBegin(SelBegin), SelEnd(SelEnd), SelFile(SelFile),
SelBeginTokenStart(SM.getFileOffset(Lexer::GetBeginningOfToken(
SM.getComposedLoc(SelFile, SelBegin), SM, LangOpts))) {
// Ensure we have a node for the TU decl, regardless of traversal scope.
@@ -148,7 +180,10 @@ private:
// Node is always a pointer so the generic code can handle any null checks.
template <typename T, typename Func>
bool traverseNode(T *Node, const Func &Body) {
- if (Node == nullptr || canSafelySkipNode(Node->getSourceRange()))
+ if (Node == nullptr)
+ return true;
+ auto N = DynTypedNode::create(*Node);
+ if (canSafelySkipNode(N))
return true;
push(DynTypedNode::create(*Node));
bool Ret = Body();
@@ -183,31 +218,41 @@ private:
// An optimization for a common case: nodes outside macro expansions that
// don't intersect the selection may be recursively skipped.
- bool canSafelySkipNode(SourceRange S) {
+ bool canSafelySkipNode(const DynTypedNode &N) {
+ SourceRange S = N.getSourceRange();
auto B = SM.getDecomposedLoc(S.getBegin());
auto E = SM.getDecomposedLoc(S.getEnd());
+ // Node lies in a macro expansion?
if (B.first != SelFile || E.first != SelFile)
return false;
- return B.second >= SelEnd || E.second < SelBeginTokenStart;
+ // Node intersects selection tokens?
+ if (B.second < SelEnd && E.second >= SelBeginTokenStart)
+ return false;
+ // Otherwise, allow skipping over the node.
+ dlog("{1}skip: {0}", printNodeToString(N, PrintPolicy), indent());
+ dlog("{1}skipped range = {0}", S.printToString(SM), indent(1));
+ return true;
}
// Pushes a node onto the ancestor stack. Pairs with pop().
// Performs early hit detection for some nodes (on the earlySourceRange).
void push(DynTypedNode Node) {
- bool SelectedEarly = claimRange(earlySourceRange(Node));
+ SourceRange Early = earlySourceRange(Node);
+ dlog("{1}push: {0}", printNodeToString(Node, PrintPolicy), indent());
Nodes.emplace_back();
Nodes.back().ASTNode = std::move(Node);
Nodes.back().Parent = Stack.top();
// Early hit detection never selects the whole node.
- Nodes.back().Selected =
- SelectedEarly ? SelectionTree::Partial : SelectionTree::Unselected;
Stack.push(&Nodes.back());
+ Nodes.back().Selected =
+ claimRange(Early) ? SelectionTree::Partial : SelectionTree::Unselected;
}
// Pops a node off the ancestor stack, and finalizes it. Pairs with push().
// Performs primary hit detection.
void pop() {
Node &N = *Stack.top();
+ dlog("{1}pop: {0}", printNodeToString(N.ASTNode, PrintPolicy), indent(-1));
if (auto Sel = claimRange(N.ASTNode.getSourceRange()))
N.Selected = Sel;
if (N.Selected || !N.Children.empty()) {
@@ -250,6 +295,7 @@ private:
// Selecting "++x" or "x" will do the right thing.
auto Range = toHalfOpenFileRange(SM, LangOpts, S);
assert(Range && "We should be able to get the File Range");
+ dlog("{1}claimRange: {0}", Range->printToString(SM), indent());
auto B = SM.getDecomposedLoc(Range->getBegin());
auto E = SM.getDecomposedLoc(Range->getEnd());
// Otherwise, nodes in macro expansions can't be selected.
@@ -269,6 +315,11 @@ private:
// children were selected.
if (!Claimed.add(B.second, E.second))
return SelectionTree::Unselected;
+ dlog("{1}hit selection: {0}",
+ SourceRange(SM.getComposedLoc(B.first, B.second),
+ SM.getComposedLoc(E.first, E.second))
+ .printToString(SM),
+ indent());
// Some of our own characters are covered, this is a true hit.
// Determine whether the node was completely covered.
return (PreciseBounds.first >= SelBegin && PreciseBounds.second <= SelEnd)
@@ -276,8 +327,16 @@ private:
: SelectionTree::Partial;
}
+ std::string indent(int Offset = 0) {
+ // Cast for signed arithmetic.
+ int Amount = int(Stack.size()) + Offset;
+ assert(Amount >= 0);
+ return std::string(Amount, ' ');
+ }
+
SourceManager &SM;
const LangOptions &LangOpts;
+ const PrintingPolicy &PrintPolicy;
std::stack<Node *> Stack;
RangeSet Claimed;
std::deque<Node> Nodes; // Stable pointers as we add more nodes.
@@ -302,18 +361,7 @@ void SelectionTree::print(llvm::raw_ostr
: '.');
else
OS.indent(Indent);
- if (const TypeLoc *TL = N.ASTNode.get<TypeLoc>()) {
- // TypeLoc is a hierarchy, but has only a single ASTNodeKind.
- // Synthesize the name from the Type subclass (except for QualifiedTypeLoc).
- if (TL->getTypeLocClass() == TypeLoc::Qualified)
- OS << "QualifiedTypeLoc";
- else
- OS << TL->getType()->getTypeClassName() << "TypeLoc";
- } else {
- OS << N.ASTNode.getNodeKind().asStringRef();
- }
- OS << " ";
- N.ASTNode.print(OS, PrintPolicy);
+ printNode(OS, N.ASTNode, PrintPolicy);
OS << "\n";
for (const Node *Child : N.Children)
print(OS, *Child, Indent + 2);
@@ -342,14 +390,19 @@ SelectionTree::SelectionTree(ASTContext
: PrintPolicy(AST.getLangOpts()) {
// No fundamental reason the selection needs to be in the main file,
// but that's all clangd has needed so far.
- FileID FID = AST.getSourceManager().getMainFileID();
+ const SourceManager &SM = AST.getSourceManager();
+ FileID FID = SM.getMainFileID();
if (Begin == End)
std::tie(Begin, End) = pointBounds(Begin, FID, AST);
PrintPolicy.TerseOutput = true;
PrintPolicy.IncludeNewlines = false;
- Nodes = SelectionVisitor::collect(AST, Begin, End, FID);
+ dlog("Computing selection for {0}",
+ SourceRange(SM.getComposedLoc(FID, Begin), SM.getComposedLoc(FID, End))
+ .printToString(SM));
+ Nodes = SelectionVisitor::collect(AST, PrintPolicy, Begin, End, FID);
Root = Nodes.empty() ? nullptr : &Nodes.front();
+ dlog("Built selection tree\n{0}", *this);
}
SelectionTree::SelectionTree(ASTContext &AST, unsigned Offset)
Modified: clang-tools-extra/trunk/clangd/Selection.h
URL: http://llvm.org/viewvc/llvm-project/clang-tools-extra/trunk/clangd/Selection.h?rev=366698&r1=366697&r2=366698&view=diff
==============================================================================
--- clang-tools-extra/trunk/clangd/Selection.h (original)
+++ clang-tools-extra/trunk/clangd/Selection.h Mon Jul 22 08:55:53 2019
@@ -97,7 +97,6 @@ public:
// which is not the node itself.
const DeclContext& getDeclContext() const;
};
-
// The most specific common ancestor of all the selected nodes.
// If there is no selection, this is nullptr.
const Node *commonAncestor() const;
More information about the cfe-commits
mailing list