This is an archive of the discontinued LLVM Phabricator instance.

[clangd] Avoid recursion in TargetFinder::add()
ClosedPublic

Authored by nridge on Jan 10 2021, 5:49 PM.

Diff Detail

Event Timeline

nridge created this revision.Jan 10 2021, 5:49 PM
nridge requested review of this revision.Jan 10 2021, 5:49 PM
nridge added inline comments.Jan 10 2021, 5:53 PM
clang-tools-extra/clangd/unittests/FindTargetTests.cpp
790

This test case is not strictly related to the bug, but it adds test coverage for a scenario that I think is important, and that could be broken if we took a different approach to fixing this bug (see my comments on the issue for details).

njames93 retitled this revision from [clangd] Avoid recusion in TargetFinder::add() to [clangd] Avoid recursion in TargetFinder::add().Jan 10 2021, 5:56 PM
njames93 added inline comments.
clang-tools-extra/clangd/FindTarget.cpp
333

Whats the purpose in tracking this? Is the Decls lookup not enough?

361–364

Couldn't this just be a map lookup?

nridge marked an inline comment as done.Jan 10 2021, 6:29 PM
nridge added inline comments.
clang-tools-extra/clangd/FindTarget.cpp
333

No, because the possibly-recursive call is here, but we only insert into Decls here.

361–364

Yup, good point!

nridge updated this revision to Diff 315689.Jan 10 2021, 6:29 PM
nridge marked an inline comment as done.

Use map lookup instead of iteration

kadircet added inline comments.Jan 10 2021, 10:54 PM
clang-tools-extra/clangd/FindTarget.cpp
376

rather than introducing a new member for CurrentlyProcessing, can't we just get away with checking the decl we are going to jump on here (and probably in namespace alias case) are the same (or maybe a parameter to add function indicating the parent/previous)?

it is definitely cleaner to have a member rather than checking individually but we have other types of ast nodes we can be currently processing (statements, nested namespecifiers etc. and they are not covered by this member. so it is a little bit confusing conceptually.

oh and also thanks a lot for all the investigation and fix of the issue!

nridge added inline comments.Jan 10 2021, 11:06 PM
clang-tools-extra/clangd/FindTarget.cpp
376

It takes a few steps to get back the recursive declaration:

  • we start with the original declaration D
  • we cast it to TypedefNameDecl here, call getUnderlyingType() and call add(QualType)
  • in add(QualType), we construct a TypeVisitor and call Visit()
  • the visitor gets into VisitDependentNameType(), calls getMembersReferencedViaDependentName(), and that gets us back the same declaration D which we call add(Decl) on

So, we'd have to propagate the "previous decl" into add(QualType) and make it a member of the TypeVisitor at least, to be able to check there.

Doh, I really thought we'd get away without an explicit recursion guard here.
But the example is compelling, so this seems like the right approach. Unfortunately, I think we're going to end up needing to add allocations too...

clang-tools-extra/clangd/FindTarget.cpp
333

I don't think a single pointer is going to cut it here - can't we construct a mutually-recursive example so that the "wrong" decl is on always on top of the stack and we never bail out?

We probably want a SmallDenseMap Seen or something? (And then we no longer need to look up in the actual result set).

Couple of related refinements:

  • Currently when we reach the same node via multiple flag paths, we take the union of the flags. (I'm not sure this is *great* behavior, but the API is pretty limiting). To be consistent with that here, we should store the flags for seen too, and union them when updating, and only bail out if we added no new flags. (This will almost never matter in practice, but it'd be nice not to worry)
  • You could consider making the key type void* instead of decl* and also break loops with no decls in them. But this can certainly wait until we find such a loop!
333

No, because the possibly-recursive call is here, but we only insert into Decls here.

This is just for implementation convenience, we could reverse the order by rearranging the code somehow. However this seems fragile to me - I'm thinking about the cases where we reassign D in the function of the body here, and so end up reporting some other declaration instead (which may not be in the cycle).
So I think I like the solution in this patch better.

nridge updated this revision to Diff 316000.Jan 11 2021, 11:37 PM

Address review comments

sammccall accepted this revision.Jan 12 2021, 4:38 AM
This revision is now accepted and ready to land.Jan 12 2021, 4:38 AM
This revision was automatically updated to reflect the committed changes.