Skip to content

Commit 56bdb0c

Browse files
committed
[clangd] Compute scopes eagerly in IncludeFixer
Summary: Computing lazily leads to crashes. In particular, computing scopes may produce diagnostics (from inside template instantiations) and we currently do it when processing another diagnostic, which leads to crashes. Moreover, we remember and access 'Scope*' when computing scopes. This might lead to invalid memory access if the Scope is deleted by the time we run the delayed computation. We did not actually construct an example when this happens, though. From the VCS and review history, it seems the optimization was introduced in the initial version without a mention of any performance benchmarks justifying the performance gains. This led me to a conclusion that the optimization was premature, so removing it to avoid crashes seems like the right trade-off at that point. Reviewers: sammccall Reviewed By: sammccall Subscribers: MaskRay, jkorous, arphaman, kadircet, cfe-commits Tags: #clang Differential Revision: https://reviews.llvm.org/D65796 llvm-svn: 368019
1 parent 2836cf0 commit 56bdb0c

File tree

3 files changed

+57
-37
lines changed

3 files changed

+57
-37
lines changed

clang-tools-extra/clangd/IncludeFixer.cpp

Lines changed: 35 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
#include "index/Symbol.h"
1717
#include "clang/AST/Decl.h"
1818
#include "clang/AST/DeclBase.h"
19+
#include "clang/AST/DeclarationName.h"
1920
#include "clang/AST/NestedNameSpecifier.h"
2021
#include "clang/AST/Type.h"
2122
#include "clang/Basic/Diagnostic.h"
@@ -34,6 +35,7 @@
3435
#include "llvm/ADT/DenseMap.h"
3536
#include "llvm/ADT/None.h"
3637
#include "llvm/ADT/Optional.h"
38+
#include "llvm/ADT/StringExtras.h"
3739
#include "llvm/ADT/StringRef.h"
3840
#include "llvm/ADT/StringSet.h"
3941
#include "llvm/Support/Error.h"
@@ -301,6 +303,24 @@ llvm::Optional<CheapUnresolvedName> extractUnresolvedNameCheaply(
301303
return Result;
302304
}
303305

306+
/// Returns all namespace scopes that the unqualified lookup would visit.
307+
std::vector<std::string>
308+
collectAccessibleScopes(Sema &Sem, const DeclarationNameInfo &Typo, Scope *S,
309+
Sema::LookupNameKind LookupKind) {
310+
std::vector<std::string> Scopes;
311+
VisitedContextCollector Collector;
312+
Sem.LookupVisibleDecls(S, LookupKind, Collector,
313+
/*IncludeGlobalScope=*/false,
314+
/*LoadExternal=*/false);
315+
316+
Scopes.push_back("");
317+
for (const auto *Ctx : Collector.takeVisitedContexts()) {
318+
if (isa<NamespaceDecl>(Ctx))
319+
Scopes.push_back(printNamespaceScope(*Ctx));
320+
}
321+
return Scopes;
322+
}
323+
304324
class IncludeFixer::UnresolvedNameRecorder : public ExternalSemaSource {
305325
public:
306326
UnresolvedNameRecorder(llvm::Optional<UnresolvedName> &LastUnresolvedName)
@@ -321,48 +341,30 @@ class IncludeFixer::UnresolvedNameRecorder : public ExternalSemaSource {
321341
if (!isInsideMainFile(Typo.getLoc(), SemaPtr->SourceMgr))
322342
return clang::TypoCorrection();
323343

324-
// This is not done lazily because `SS` can get out of scope and it's
325-
// relatively cheap.
326344
auto Extracted = extractUnresolvedNameCheaply(
327345
SemaPtr->SourceMgr, Typo, SS, SemaPtr->LangOpts,
328346
static_cast<Sema::LookupNameKind>(LookupKind) ==
329347
Sema::LookupNameKind::LookupNestedNameSpecifierName);
330348
if (!Extracted)
331349
return TypoCorrection();
332-
auto CheapUnresolved = std::move(*Extracted);
350+
333351
UnresolvedName Unresolved;
334-
Unresolved.Name = CheapUnresolved.Name;
352+
Unresolved.Name = Extracted->Name;
335353
Unresolved.Loc = Typo.getBeginLoc();
336-
337-
if (!CheapUnresolved.ResolvedScope && !S) // Give up if no scope available.
354+
if (!Extracted->ResolvedScope && !S) // Give up if no scope available.
338355
return TypoCorrection();
339356

340-
auto *Sem = SemaPtr; // Avoid capturing `this`.
341-
Unresolved.GetScopes = [Sem, CheapUnresolved, S, LookupKind]() {
342-
std::vector<std::string> Scopes;
343-
if (CheapUnresolved.ResolvedScope) {
344-
Scopes.push_back(*CheapUnresolved.ResolvedScope);
345-
} else {
346-
assert(S);
347-
// No scope specifier is specified. Collect all accessible scopes in the
348-
// context.
349-
VisitedContextCollector Collector;
350-
Sem->LookupVisibleDecls(
351-
S, static_cast<Sema::LookupNameKind>(LookupKind), Collector,
352-
/*IncludeGlobalScope=*/false,
353-
/*LoadExternal=*/false);
354-
355-
Scopes.push_back("");
356-
for (const auto *Ctx : Collector.takeVisitedContexts())
357-
if (isa<NamespaceDecl>(Ctx))
358-
Scopes.push_back(printNamespaceScope(*Ctx));
359-
}
357+
if (Extracted->ResolvedScope)
358+
Unresolved.Scopes.push_back(*Extracted->ResolvedScope);
359+
else // no qualifier or qualifier is unresolved.
360+
Unresolved.Scopes = collectAccessibleScopes(
361+
*SemaPtr, Typo, S, static_cast<Sema::LookupNameKind>(LookupKind));
362+
363+
if (Extracted->UnresolvedScope) {
364+
for (std::string &Scope : Unresolved.Scopes)
365+
Scope += *Extracted->UnresolvedScope;
366+
}
360367

361-
if (CheapUnresolved.UnresolvedScope)
362-
for (auto &Scope : Scopes)
363-
Scope.append(*CheapUnresolved.UnresolvedScope);
364-
return Scopes;
365-
};
366368
LastUnresolvedName = std::move(Unresolved);
367369

368370
// Never return a valid correction to try to recover. Our suggested fixes
@@ -384,14 +386,13 @@ IncludeFixer::unresolvedNameRecorder() {
384386
std::vector<Fix> IncludeFixer::fixUnresolvedName() const {
385387
assert(LastUnresolvedName.hasValue());
386388
auto &Unresolved = *LastUnresolvedName;
387-
std::vector<std::string> Scopes = Unresolved.GetScopes();
388389
vlog("Trying to fix unresolved name \"{0}\" in scopes: [{1}]",
389-
Unresolved.Name, llvm::join(Scopes.begin(), Scopes.end(), ", "));
390+
Unresolved.Name, llvm::join(Unresolved.Scopes, ", "));
390391

391392
FuzzyFindRequest Req;
392393
Req.AnyScope = false;
393394
Req.Query = Unresolved.Name;
394-
Req.Scopes = Scopes;
395+
Req.Scopes = Unresolved.Scopes;
395396
Req.RestrictForCodeCompletion = true;
396397
Req.Limit = 100;
397398

clang-tools-extra/clangd/IncludeFixer.h

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -58,9 +58,7 @@ class IncludeFixer {
5858
struct UnresolvedName {
5959
std::string Name; // E.g. "X" in foo::X.
6060
SourceLocation Loc; // Start location of the unresolved name.
61-
// Lazily get the possible scopes of the unresolved name. `Sema` must be
62-
// alive when this is called.
63-
std::function<std::vector<std::string>()> GetScopes;
61+
std::vector<std::string> Scopes; // Namespace scopes we should search in.
6462
};
6563

6664
/// Records the last unresolved name seen by Sema.

clang-tools-extra/clangd/unittests/DiagnosticsTests.cpp

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -797,6 +797,27 @@ namespace c {
797797
"Add include \"x.h\" for symbol a::X")))));
798798
}
799799

800+
TEST(IncludeFixerTest, NoCrashOnTemplateInstantiations) {
801+
Annotations Test(R"cpp(
802+
template <typename T> struct Templ {
803+
template <typename U>
804+
typename U::type operator=(const U &);
805+
};
806+
807+
struct A {
808+
Templ<char> s;
809+
A() { [[a]]; } // this caused crashes if we compute scopes lazily.
810+
};
811+
)cpp");
812+
813+
auto TU = TestTU::withCode(Test.code());
814+
auto Index = buildIndexWithSymbol({});
815+
TU.ExternalIndex = Index.get();
816+
817+
EXPECT_THAT(TU.build().getDiagnostics(),
818+
ElementsAre(Diag(Test.range(), "use of undeclared identifier 'a'")));
819+
}
820+
800821
TEST(DiagsInHeaders, DiagInsideHeader) {
801822
Annotations Main(R"cpp(
802823
#include [["a.h"]]

0 commit comments

Comments
 (0)