This is an archive of the discontinued LLVM Phabricator instance.

[clangd] Support renaming virtual methods
ClosedPublic

Authored by tom-anders on Aug 27 2022, 2:10 PM.

Diff Detail

Event Timeline

tom-anders created this revision.Aug 27 2022, 2:10 PM
Herald added a project: Restricted Project. · View Herald TranscriptAug 27 2022, 2:10 PM
tom-anders requested review of this revision.Aug 27 2022, 2:10 PM

Sorry for the delay, this patch is really neat!

The only serious thing is it uncovers an existing bug which asserts. As a potential new crash I think we should fix that - LMK whether you feel comfortable adding that fix in here or you'd like me to do it as a separate review.

clang-tools-extra/clangd/refactor/Rename.cpp
245

Unfortunately we've uncovered a bug: this assertion fires (after this patch) on the following code:

template <typename> class Foo { virtual void m(); };
class Bar : Foo<int> { void ^m() override; };

canonicalRenameDecl is supposed to be idempotent. However:canonicalRenameDecl(Bar::m) is Foo<int>::m, but canonicalRenameDecl(Foo<int>::m) is Foo<T>::m.

I think this is because the getInstantiatedFromMemberFunction check comes before the overridden_methods check, so if the override points to a member of an instantiation then it's too late to map it back to the member of the template.

Probably the best fix is to change line 103 to a recursive return canonicalRenameDecl(...), as is done elsewhere in the function.

(Can you please add this case to the tests?)

547

we're already in an anon namespace here

548

The fact that we only need to walk down wasn't obvious to me at first, maybe add a comment on this function? e.g.

Walk down from a virtual method to overriding methods, we rename them as a group.
Note that canonicalRenameDecl() ensures we're starting from the base method.

Thanks for the patch! A drive-by comment from me, hopefully useful.

clang-tools-extra/clangd/refactor/Rename.cpp
554

Should we put a limit on the number of requests we send during recursion here?

I see a few obvious failure modes:

  • infinite recursion in the relations due to parts of index being stale, corrupted input data or other reasons,
  • exponential blow up in hierarchies with multiple inheritance,
  • sending a lot of network requests in case of deep inheritance hierarchies for remote index implementations. Since all requests are sequential, the network latency might add up to substantial numbers.

We could address these in some other manner, this just seems to be the simplest option to protect against catastrophic outcomes (running the request indefinitely, crashing due to infinite recursion, etc).

sammccall added inline comments.Sep 5 2022, 6:03 AM
clang-tools-extra/clangd/refactor/Rename.cpp
548

nit: can pass SymbolID by value, it's 8 bytes

554

exponential blow up in hierarchies with multiple inheritance,

It seems with little loss of readability we could provide some useful bounds:

DenseSet<SymbolID> Pending = {Base};
while (!Pending.empty()) {
  Req = {.Subjects = Pending};
  Pending.clear();
  Index.relations(Req, { IDs.insert(ID); Pending.insert(ID) });
}

in this case the #requests is clearly bounded by the length of the shortest chain to the most distant SymbolID, and is IMO safe with no numeric cap.

whereas the current version could potentially get the same ID in multiple branches and so the bound on #requests is harder to determine.

ilya-biryukov added inline comments.Sep 5 2022, 6:20 AM
clang-tools-extra/clangd/refactor/Rename.cpp
554

This looks good! Also avoids infinite recursion.
Having a potentially large number of sequential network requests still looks unfortunate, but I doubt this will bite us in practice (at least not for remote indicies for LLVM and Chrome).

To solve it, we could allow recursive requests and implement the recursion inside the index, but this could be done with a follow-up when we actually hit this issue.

Add test case that triggers assertion, limit recursion

tom-anders marked 6 inline comments as done.Sep 6 2022, 11:14 AM
tom-anders added inline comments.
clang-tools-extra/clangd/refactor/Rename.cpp
245

I added the test case for this. However, your suggested fix doesn't seem to stop the assertion from being triggered. I'd be grateful if you can take a closer look at this in a separate review - Should I leave the test case in anyway? Is it okay if the test fails for a while until you found the fix?

554

Done, hope I interpreted your sketch correctly

tom-anders updated this revision to Diff 458270.Sep 6 2022, 1:57 PM
tom-anders marked an inline comment as done.

Fix recursivelyInsertRenames()

tom-anders added inline comments.Sep 6 2022, 1:58 PM
clang-tools-extra/clangd/refactor/Rename.cpp
554

Nope, I totally misunderstood, should be better now

A few more NITs from me, but please wait for @sammccall for another round of reviews.

clang-tools-extra/clangd/refactor/Rename.cpp
550

NIT: maybe rename to insertTransitiveOverrides?
This function is not recursive anymore and "transitive" seems to be a more precise term for describing the results of running this function.

561–562

This should protect against infinite loop in case of broken/stale index.

sammccall accepted this revision.Sep 7 2022, 4:15 AM

LG

IIRC Tom doesn't have commit access, so I'll apply the last trivial changes and commit to avoid another round trip.
(Please LMK if i'm wrong about this!)

clang-tools-extra/clangd/refactor/Rename.cpp
245

Thanks for adding the test, and sorry about the bad guess at a fix. We can't turn the buildbots red, so I'll comment out the test and fix+enable it separately.

561–562

Good catch!

This revision is now accepted and ready to land.Sep 7 2022, 4:15 AM
This revision was landed with ongoing or failed builds.Sep 7 2022, 4:16 AM
This revision was automatically updated to reflect the committed changes.

LG

IIRC Tom doesn't have commit access, so I'll apply the last trivial changes and commit to avoid another round trip.
(Please LMK if i'm wrong about this!)

I actually have commit access now, but thanks :) looking forward to your follow-up