Skip to content

Commit 62e2472

Browse files
committed
[clangd] Include insertion: require header guards, drop other heuristics, treat .def like .inc.
Summary: We do have some reports of include insertion behaving badly in some codebases. Requiring header guards both makes sense in principle, and is likely to disable this "nice-to-have" feature in codebases where headers don't follow the expected pattern. With this we can drop some other heuristics, such as looking at file extensions to detect known non-headers - implementation files have no guards. One wrinkle here is #import - objc headers may not have guards because they're intended to be used via #import. If the header is the main file or is #included, we won't collect locations - merge should take care of this if we see the file #imported somewhere. Seems likely to be OK. Headers which have a canonicalization (stdlib, IWYU) are exempt from this check. *.inc files continue to be handled by looking up to the including file. This patch also adds *.def here - tablegen wants this pattern too. In terms of code structure, the division between SymbolCollector and CanonicalIncludes has shifted: SymbolCollector is responsible for more. This is because SymbolCollector has all the SourceManager/HeaderSearch access needed for checking for guards, and we interleave these checks with the *.def checks in a loop (potentially). We could hand all the info into CanonicalIncludes and put the logic there if that's preferable. Reviewers: ioeric Subscribers: ilya-biryukov, MaskRay, jkorous, arphaman, kadircet, cfe-commits Tags: #clang Differential Revision: https://reviews.llvm.org/D60316 llvm-svn: 358571
1 parent 6fe637c commit 62e2472

File tree

8 files changed

+105
-139
lines changed

8 files changed

+105
-139
lines changed

clang-tools-extra/clangd/index/CanonicalIncludes.cpp

Lines changed: 2 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -37,31 +37,12 @@ void CanonicalIncludes::addSymbolMapping(llvm::StringRef QualifiedName,
3737
}
3838

3939
llvm::StringRef
40-
CanonicalIncludes::mapHeader(llvm::ArrayRef<std::string> Headers,
40+
CanonicalIncludes::mapHeader(llvm::StringRef Header,
4141
llvm::StringRef QualifiedName) const {
42-
assert(!Headers.empty());
42+
assert(!Header.empty());
4343
auto SE = SymbolMapping.find(QualifiedName);
4444
if (SE != SymbolMapping.end())
4545
return SE->second;
46-
// Find the first header such that the extension is not '.inc', and isn't a
47-
// recognized non-header file
48-
auto I = llvm::find_if(Headers, [](llvm::StringRef Include) {
49-
// Skip .inc file whose including header file should
50-
// be #included instead.
51-
return !Include.endswith(".inc");
52-
});
53-
if (I == Headers.end())
54-
return Headers[0]; // Fallback to the declaring header.
55-
llvm::StringRef Header = *I;
56-
// If Header is not expected be included (e.g. .cc file), we fall back to
57-
// the declaring header.
58-
llvm::StringRef Ext = llvm::sys::path::extension(Header).trim('.');
59-
// Include-able headers must have precompile type. Treat files with
60-
// non-recognized extenstions (TY_INVALID) as headers.
61-
auto ExtType = driver::types::lookupTypeForExtension(Ext);
62-
if ((ExtType != driver::types::TY_INVALID) &&
63-
!driver::types::onlyPrecompileType(ExtType))
64-
return Headers[0];
6546

6647
auto MapIt = FullPathMapping.find(Header);
6748
if (MapIt != FullPathMapping.end())

clang-tools-extra/clangd/index/CanonicalIncludes.h

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -50,9 +50,9 @@ class CanonicalIncludes {
5050
llvm::StringRef CanonicalPath);
5151

5252
/// Returns the canonical include for symbol with \p QualifiedName.
53-
/// \p Headers is the include stack: Headers.front() is the file declaring the
54-
/// symbol, and Headers.back() is the main file.
55-
llvm::StringRef mapHeader(llvm::ArrayRef<std::string> Headers,
53+
/// \p Header is the file the declaration was reachable from.
54+
/// Header itself will be returned if there is no relevant mapping.
55+
llvm::StringRef mapHeader(llvm::StringRef Header,
5656
llvm::StringRef QualifiedName) const;
5757

5858
private:

clang-tools-extra/clangd/index/SymbolCollector.cpp

Lines changed: 42 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
#include "clang/Basic/Specifiers.h"
2626
#include "clang/Index/IndexSymbol.h"
2727
#include "clang/Index/USRGeneration.h"
28+
#include "clang/Lex/Preprocessor.h"
2829
#include "llvm/Support/Casting.h"
2930
#include "llvm/Support/FileSystem.h"
3031
#include "llvm/Support/MemoryBuffer.h"
@@ -127,34 +128,46 @@ bool shouldCollectIncludePath(index::SymbolKind Kind) {
127128
}
128129
}
129130

130-
/// Gets a canonical include (URI of the header or <header> or "header") for
131-
/// header of \p Loc.
132-
/// Returns None if fails to get include header for \p Loc.
131+
bool isSelfContainedHeader(FileID FID, const SourceManager &SM,
132+
const Preprocessor &PP) {
133+
const FileEntry *FE = SM.getFileEntryForID(FID);
134+
if (!FE)
135+
return false;
136+
return PP.getHeaderSearchInfo().isFileMultipleIncludeGuarded(FE);
137+
}
138+
139+
/// Gets a canonical include (URI of the header or <header> or "header") for
140+
/// header of \p FID (which should usually be the *expansion* file).
141+
/// Returns None if includes should not be inserted for this file.
133142
llvm::Optional<std::string>
134143
getIncludeHeader(llvm::StringRef QName, const SourceManager &SM,
135-
SourceLocation Loc, const SymbolCollector::Options &Opts) {
136-
std::vector<std::string> Headers;
137-
// Collect the #include stack.
138-
while (true) {
139-
if (!Loc.isValid())
140-
break;
141-
auto FilePath = SM.getFilename(Loc);
142-
if (FilePath.empty())
143-
break;
144-
Headers.push_back(FilePath);
145-
if (SM.isInMainFile(Loc))
146-
break;
147-
Loc = SM.getIncludeLoc(SM.getFileID(Loc));
148-
}
149-
if (Headers.empty())
150-
return None;
151-
llvm::StringRef Header = Headers[0];
144+
const Preprocessor &PP, FileID FID,
145+
const SymbolCollector::Options &Opts) {
146+
const FileEntry *FE = SM.getFileEntryForID(FID);
147+
if (!FE || FE->getName().empty())
148+
return llvm::None;
149+
llvm::StringRef Filename = FE->getName();
150+
// If a file is mapped by canonical headers, use that mapping, regardless
151+
// of whether it's an otherwise-good header (header guards etc).
152152
if (Opts.Includes) {
153-
Header = Opts.Includes->mapHeader(Headers, QName);
154-
if (Header.startswith("<") || Header.startswith("\""))
155-
return Header.str();
153+
llvm::StringRef Canonical = Opts.Includes->mapHeader(Filename, QName);
154+
// If we had a mapping, always use it.
155+
if (Canonical.startswith("<") || Canonical.startswith("\""))
156+
return Canonical.str();
157+
if (Canonical != Filename)
158+
return toURI(SM, Canonical, Opts);
159+
}
160+
if (!isSelfContainedHeader(FID, SM, PP)) {
161+
// A .inc or .def file is often included into a real header to define
162+
// symbols (e.g. LLVM tablegen files).
163+
if (Filename.endswith(".inc") || Filename.endswith(".def"))
164+
return getIncludeHeader(QName, SM, PP,
165+
SM.getFileID(SM.getIncludeLoc(FID)), Opts);
166+
// Conservatively refuse to insert #includes to files without guards.
167+
return llvm::None;
156168
}
157-
return toURI(SM, Header, Opts);
169+
// Standard case: just insert the file itself.
170+
return toURI(SM, Filename, Opts);
158171
}
159172

160173
// Return the symbol range of the token at \p TokLoc.
@@ -439,8 +452,9 @@ bool SymbolCollector::handleMacroOccurence(const IdentifierInfo *Name,
439452

440453
std::string Include;
441454
if (Opts.CollectIncludePath && shouldCollectIncludePath(S.SymInfo.Kind)) {
442-
if (auto Header = getIncludeHeader(Name->getName(), SM,
443-
SM.getExpansionLoc(DefLoc), Opts))
455+
if (auto Header =
456+
getIncludeHeader(Name->getName(), SM, *PP,
457+
SM.getDecomposedExpansionLoc(DefLoc).first, Opts))
444458
Include = std::move(*Header);
445459
}
446460
S.Signature = Signature;
@@ -588,7 +602,8 @@ const Symbol *SymbolCollector::addDeclaration(const NamedDecl &ND, SymbolID ID,
588602
// Use the expansion location to get the #include header since this is
589603
// where the symbol is exposed.
590604
if (auto Header = getIncludeHeader(
591-
QName, SM, SM.getExpansionLoc(ND.getLocation()), Opts))
605+
QName, SM, *PP,
606+
SM.getDecomposedExpansionLoc(ND.getLocation()).first, Opts))
592607
Include = std::move(*Header);
593608
}
594609
if (!Include.empty())

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -684,6 +684,7 @@ TEST(CompletionTest, DynamicIndexIncludeInsertion) {
684684
ClangdServer Server(CDB, FS, DiagConsumer, Opts);
685685

686686
FS.Files[testPath("foo_header.h")] = R"cpp(
687+
#pragma once
687688
struct Foo {
688689
// Member doc
689690
int foo();

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

Lines changed: 11 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -212,33 +212,11 @@ TEST(FileIndexTest, IncludeCollected) {
212212
}
213213

214214
TEST(FileIndexTest, HasSystemHeaderMappingsInPreamble) {
215-
FileIndex Index;
216-
const std::string Header = R"cpp(
217-
class Foo {};
218-
)cpp";
219-
auto MainFile = testPath("foo.cpp");
220-
auto HeaderFile = testPath("algorithm");
221-
std::vector<const char *> Cmd = {"clang", "-xc++", MainFile.c_str(),
222-
"-include", HeaderFile.c_str()};
223-
// Preparse ParseInputs.
224-
ParseInputs PI;
225-
PI.CompileCommand.Directory = testRoot();
226-
PI.CompileCommand.Filename = MainFile;
227-
PI.CompileCommand.CommandLine = {Cmd.begin(), Cmd.end()};
228-
PI.Contents = "";
229-
PI.FS = buildTestFS({{MainFile, ""}, {HeaderFile, Header}});
215+
TestTU TU;
216+
TU.HeaderCode = "class Foo{};";
217+
TU.HeaderFilename = "algorithm";
230218

231-
// Prepare preamble.
232-
auto CI = buildCompilerInvocation(PI);
233-
auto PreambleData =
234-
buildPreamble(MainFile, *buildCompilerInvocation(PI),
235-
/*OldPreamble=*/nullptr, tooling::CompileCommand(), PI,
236-
/*StoreInMemory=*/true,
237-
[&](ASTContext &Ctx, std::shared_ptr<Preprocessor> PP,
238-
const CanonicalIncludes &Includes) {
239-
Index.updatePreamble(MainFile, Ctx, PP, Includes);
240-
});
241-
auto Symbols = runFuzzyFind(Index, "");
219+
auto Symbols = runFuzzyFind(*TU.index(), "");
242220
EXPECT_THAT(Symbols, ElementsAre(_));
243221
EXPECT_THAT(Symbols.begin()->IncludeHeaders.front().IncludeHeader,
244222
"<algorithm>");
@@ -369,50 +347,23 @@ TEST(FileIndexTest, CollectMacros) {
369347
}
370348

371349
TEST(FileIndexTest, ReferencesInMainFileWithPreamble) {
372-
const std::string Header = R"cpp(
373-
class Foo {};
374-
)cpp";
350+
TestTU TU;
351+
TU.HeaderCode = "class Foo{};";
375352
Annotations Main(R"cpp(
376353
#include "foo.h"
377354
void f() {
378355
[[Foo]] foo;
379356
}
380357
)cpp");
381-
auto MainFile = testPath("foo.cpp");
382-
auto HeaderFile = testPath("foo.h");
383-
std::vector<const char *> Cmd = {"clang", "-xc++", MainFile.c_str()};
384-
// Preparse ParseInputs.
385-
ParseInputs PI;
386-
PI.CompileCommand.Directory = testRoot();
387-
PI.CompileCommand.Filename = MainFile;
388-
PI.CompileCommand.CommandLine = {Cmd.begin(), Cmd.end()};
389-
PI.Contents = Main.code();
390-
PI.FS = buildTestFS({{MainFile, Main.code()}, {HeaderFile, Header}});
391-
392-
// Prepare preamble.
393-
auto CI = buildCompilerInvocation(PI);
394-
auto PreambleData =
395-
buildPreamble(MainFile, *buildCompilerInvocation(PI),
396-
/*OldPreamble=*/nullptr, tooling::CompileCommand(), PI,
397-
/*StoreInMemory=*/true,
398-
[&](ASTContext &Ctx, std::shared_ptr<Preprocessor> PP,
399-
const CanonicalIncludes &) {});
400-
// Build AST for main file with preamble.
401-
auto AST =
402-
ParsedAST::build(createInvocationFromCommandLine(Cmd), PreambleData,
403-
llvm::MemoryBuffer::getMemBufferCopy(Main.code()), PI.FS,
404-
/*Index=*/nullptr, ParseOptions());
405-
ASSERT_TRUE(AST);
358+
TU.Code = Main.code();
359+
auto AST = TU.build();
406360
FileIndex Index;
407-
Index.updateMain(MainFile, *AST);
408-
409-
auto Foo = findSymbol(TestTU::withHeaderCode(Header).headerSymbols(), "Foo");
410-
RefsRequest Request;
411-
Request.IDs.insert(Foo.ID);
361+
Index.updateMain(testPath(TU.Filename), AST);
412362

413363
// Expect to see references in main file, references in headers are excluded
414364
// because we only index main AST.
415-
EXPECT_THAT(getRefs(Index, Foo.ID), RefsAre({RefRange(Main.range())}));
365+
EXPECT_THAT(getRefs(Index, findSymbol(TU.headerSymbols(), "Foo").ID),
366+
RefsAre({RefRange(Main.range())}));
416367
}
417368

418369
} // namespace

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

Lines changed: 32 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
#include "llvm/ADT/StringRef.h"
2121
#include "llvm/Support/MemoryBuffer.h"
2222
#include "llvm/Support/VirtualFileSystem.h"
23+
#include "gmock/gmock-more-matchers.h"
2324
#include "gmock/gmock.h"
2425
#include "gtest/gtest.h"
2526

@@ -33,7 +34,7 @@ namespace {
3334
using testing::_;
3435
using testing::AllOf;
3536
using testing::Contains;
36-
using testing::Eq;
37+
using testing::ElementsAre;
3738
using testing::Field;
3839
using testing::IsEmpty;
3940
using testing::Not;
@@ -58,6 +59,7 @@ MATCHER_P(DeclURI, P, "") {
5859
return StringRef(arg.CanonicalDeclaration.FileURI) == P;
5960
}
6061
MATCHER_P(DefURI, P, "") { return StringRef(arg.Definition.FileURI) == P; }
62+
MATCHER(IncludeHeader, "") { return !arg.IncludeHeaders.empty(); }
6163
MATCHER_P(IncludeHeader, P, "") {
6264
return (arg.IncludeHeaders.size() == 1) &&
6365
(arg.IncludeHeaders.begin()->IncludeHeader == P);
@@ -249,6 +251,8 @@ class SymbolCollectorTest : public ::testing::Test {
249251
TestFileURI = URI::create(TestFileName).toString();
250252
}
251253

254+
// Note that unlike TestTU, no automatic header guard is added.
255+
// HeaderCode should start with #pragma once to be treated as modular.
252256
bool runSymbolCollector(llvm::StringRef HeaderCode, llvm::StringRef MainCode,
253257
const std::vector<std::string> &ExtraArgs = {}) {
254258
llvm::IntrusiveRefCntPtr<FileManager> Files(
@@ -920,7 +924,7 @@ TEST_F(SymbolCollectorTest, Snippet) {
920924

921925
TEST_F(SymbolCollectorTest, IncludeHeaderSameAsFileURI) {
922926
CollectorOpts.CollectIncludePath = true;
923-
runSymbolCollector("class Foo {};", /*Main=*/"");
927+
runSymbolCollector("#pragma once\nclass Foo {};", /*Main=*/"");
924928
EXPECT_THAT(Symbols, UnorderedElementsAre(
925929
AllOf(QName("Foo"), DeclURI(TestHeaderURI))));
926930
EXPECT_THAT(Symbols.begin()->IncludeHeaders,
@@ -1020,53 +1024,54 @@ TEST_F(SymbolCollectorTest, SkipIncFileWhenCanonicalizeHeaders) {
10201024

10211025
TEST_F(SymbolCollectorTest, MainFileIsHeaderWhenSkipIncFile) {
10221026
CollectorOpts.CollectIncludePath = true;
1023-
CanonicalIncludes Includes;
1024-
CollectorOpts.Includes = &Includes;
1025-
TestFileName = testPath("main.h");
1027+
// To make this case as hard as possible, we won't tell clang main is a
1028+
// header. No extension, no -x c++-header.
1029+
TestFileName = testPath("no_ext_main");
10261030
TestFileURI = URI::create(TestFileName).toString();
10271031
auto IncFile = testPath("test.inc");
10281032
auto IncURI = URI::create(IncFile).toString();
10291033
InMemoryFileSystem->addFile(IncFile, 0,
10301034
llvm::MemoryBuffer::getMemBuffer("class X {};"));
1031-
runSymbolCollector("", /*Main=*/"#include \"test.inc\"",
1035+
runSymbolCollector("", R"cpp(
1036+
// Can't use #pragma once in a main file clang doesn't think is a header.
1037+
#ifndef MAIN_H_
1038+
#define MAIN_H_
1039+
#include "test.inc"
1040+
#endif
1041+
)cpp",
10321042
/*ExtraArgs=*/{"-I", testRoot()});
10331043
EXPECT_THAT(Symbols, UnorderedElementsAre(AllOf(QName("X"), DeclURI(IncURI),
10341044
IncludeHeader(TestFileURI))));
10351045
}
10361046

1037-
TEST_F(SymbolCollectorTest, MainFileIsHeaderWithoutExtensionWhenSkipIncFile) {
1047+
TEST_F(SymbolCollectorTest, IncFileInNonHeader) {
10381048
CollectorOpts.CollectIncludePath = true;
1039-
CanonicalIncludes Includes;
1040-
CollectorOpts.Includes = &Includes;
1041-
TestFileName = testPath("no_ext_main");
1049+
TestFileName = testPath("main.cc");
10421050
TestFileURI = URI::create(TestFileName).toString();
10431051
auto IncFile = testPath("test.inc");
10441052
auto IncURI = URI::create(IncFile).toString();
10451053
InMemoryFileSystem->addFile(IncFile, 0,
10461054
llvm::MemoryBuffer::getMemBuffer("class X {};"));
1047-
runSymbolCollector("", /*Main=*/"#include \"test.inc\"",
1055+
runSymbolCollector("", R"cpp(
1056+
#include "test.inc"
1057+
)cpp",
10481058
/*ExtraArgs=*/{"-I", testRoot()});
10491059
EXPECT_THAT(Symbols, UnorderedElementsAre(AllOf(QName("X"), DeclURI(IncURI),
1050-
IncludeHeader(TestFileURI))));
1060+
Not(IncludeHeader()))));
10511061
}
10521062

1053-
TEST_F(SymbolCollectorTest, FallbackToIncFileWhenIncludingFileIsCC) {
1054-
CollectorOpts.CollectIncludePath = true;
1055-
CanonicalIncludes Includes;
1056-
CollectorOpts.Includes = &Includes;
1057-
auto IncFile = testPath("test.inc");
1058-
auto IncURI = URI::create(IncFile).toString();
1059-
InMemoryFileSystem->addFile(IncFile, 0,
1060-
llvm::MemoryBuffer::getMemBuffer("class X {};"));
1061-
runSymbolCollector("", /*Main=*/"#include \"test.inc\"",
1062-
/*ExtraArgs=*/{"-I", testRoot()});
1063-
EXPECT_THAT(Symbols, UnorderedElementsAre(AllOf(QName("X"), DeclURI(IncURI),
1064-
IncludeHeader(IncURI))));
1063+
TEST_F(SymbolCollectorTest, NonModularHeader) {
1064+
auto TU = TestTU::withHeaderCode("int x();");
1065+
EXPECT_THAT(TU.headerSymbols(), ElementsAre(IncludeHeader()));
1066+
1067+
TU.ImplicitHeaderGuard = false;
1068+
EXPECT_THAT(TU.headerSymbols(), ElementsAre(Not(IncludeHeader())));
10651069
}
10661070

10671071
TEST_F(SymbolCollectorTest, AvoidUsingFwdDeclsAsCanonicalDecls) {
10681072
CollectorOpts.CollectIncludePath = true;
10691073
Annotations Header(R"(
1074+
#pragma once
10701075
// Forward declarations of TagDecls.
10711076
class C;
10721077
struct S;
@@ -1100,7 +1105,8 @@ TEST_F(SymbolCollectorTest, AvoidUsingFwdDeclsAsCanonicalDecls) {
11001105

11011106
TEST_F(SymbolCollectorTest, ClassForwardDeclarationIsCanonical) {
11021107
CollectorOpts.CollectIncludePath = true;
1103-
runSymbolCollector(/*Header=*/"class X;", /*Main=*/"class X {};");
1108+
runSymbolCollector(/*Header=*/"#pragma once\nclass X;",
1109+
/*Main=*/"class X {};");
11041110
EXPECT_THAT(Symbols, UnorderedElementsAre(AllOf(
11051111
QName("X"), DeclURI(TestHeaderURI),
11061112
IncludeHeader(TestHeaderURI), DefURI(TestFileURI))));
@@ -1167,6 +1173,7 @@ TEST_F(SymbolCollectorTest, Origin) {
11671173
TEST_F(SymbolCollectorTest, CollectMacros) {
11681174
CollectorOpts.CollectIncludePath = true;
11691175
Annotations Header(R"(
1176+
#pragma once
11701177
#define X 1
11711178
#define $mac[[MAC]](x) int x
11721179
#define $used[[USED]](y) float y;

0 commit comments

Comments
 (0)