Skip to content

Commit e8a64e5

Browse files
committed
[clang][pp] adds '#pragma include_instead'
`#pragma clang include_instead(<header>)` is a pragma that can be used by system headers (and only system headers) to indicate to a tool that the file containing said pragma is an implementation-detail header and should not be directly included by user code. The library alternative is very messy code that can be seen in the first diff of D106124, and we'd rather avoid that with something more universal. This patch takes the first step by warning a user when they include a detail header in their code, and suggests alternative headers that the user should include instead. Future work will involve adding a fixit to automate the process, as well as cleaning up modules diagnostics to not suggest said detail headers. Other tools, such as clangd can also take advantage of this pragma to add the correct user headers. Differential Revision: https://reviews.llvm.org/D106394
1 parent f921bf6 commit e8a64e5

20 files changed

+225
-23
lines changed

clang/include/clang/Basic/DiagnosticLexKinds.td

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -300,6 +300,12 @@ def pp_pragma_once_in_main_file : Warning<"#pragma once in main file">,
300300
def pp_pragma_sysheader_in_main_file : Warning<
301301
"#pragma system_header ignored in main file">,
302302
InGroup<DiagGroup<"pragma-system-header-outside-header">>;
303+
304+
def err_pragma_include_instead_not_sysheader : Error<
305+
"'#pragma clang include_instead' cannot be used outside of system headers">;
306+
def err_pragma_include_instead_system_reserved : Error<
307+
"header '%0' is an implementation detail; #include %select{'%2'|either '%2' or '%3'|one of %2}1 instead">;
308+
303309
def pp_poisoning_existing_macro : Warning<"poisoning existing macro">;
304310
def pp_out_of_date_dependency : Warning<
305311
"current file is older than dependency %0">;

clang/include/clang/Lex/HeaderSearch.h

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,12 @@
2020
#include "clang/Lex/ModuleMap.h"
2121
#include "llvm/ADT/ArrayRef.h"
2222
#include "llvm/ADT/DenseMap.h"
23+
#include "llvm/ADT/SetVector.h"
24+
#include "llvm/ADT/SmallSet.h"
25+
#include "llvm/ADT/SmallString.h"
2326
#include "llvm/ADT/StringMap.h"
24-
#include "llvm/ADT/StringSet.h"
2527
#include "llvm/ADT/StringRef.h"
28+
#include "llvm/ADT/StringSet.h"
2629
#include "llvm/Support/Allocator.h"
2730
#include <cassert>
2831
#include <cstddef>
@@ -110,6 +113,14 @@ struct HeaderFileInfo {
110113
/// of the framework.
111114
StringRef Framework;
112115

116+
/// List of aliases that this header is known as.
117+
/// Most headers should only have at most one alias, but a handful
118+
/// have two.
119+
llvm::SetVector<llvm::SmallString<32>,
120+
llvm::SmallVector<llvm::SmallString<32>, 2>,
121+
llvm::SmallSet<llvm::SmallString<32>, 2>>
122+
Aliases;
123+
113124
HeaderFileInfo()
114125
: isImport(false), isPragmaOnce(false), DirInfo(SrcMgr::C_User),
115126
External(false), isModuleHeader(false), isCompilingModuleHeader(false),
@@ -453,6 +464,10 @@ class HeaderSearch {
453464
getFileInfo(File).DirInfo = SrcMgr::C_System;
454465
}
455466

467+
void AddFileAlias(const FileEntry *File, StringRef Alias) {
468+
getFileInfo(File).Aliases.insert(Alias);
469+
}
470+
456471
/// Mark the specified file as part of a module.
457472
void MarkFileModuleHeader(const FileEntry *FE,
458473
ModuleMap::ModuleHeaderRole Role,

clang/include/clang/Lex/Preprocessor.h

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1953,7 +1953,8 @@ class Preprocessor {
19531953
/// This either returns the EOF token and returns true, or
19541954
/// pops a level off the include stack and returns false, at which point the
19551955
/// client should call lex again.
1956-
bool HandleEndOfFile(Token &Result, bool isEndOfMacro = false);
1956+
bool HandleEndOfFile(Token &Result, SourceLocation Loc,
1957+
bool isEndOfMacro = false);
19571958

19581959
/// Callback invoked when the current TokenLexer hits the end of its
19591960
/// token stream.
@@ -2363,12 +2364,14 @@ class Preprocessor {
23632364

23642365
// Pragmas.
23652366
void HandlePragmaDirective(PragmaIntroducer Introducer);
2367+
void ResolvePragmaIncludeInstead(SourceLocation Location) const;
23662368

23672369
public:
23682370
void HandlePragmaOnce(Token &OnceTok);
23692371
void HandlePragmaMark(Token &MarkTok);
23702372
void HandlePragmaPoison();
23712373
void HandlePragmaSystemHeader(Token &SysHeaderTok);
2374+
void HandlePragmaIncludeInstead(Token &Tok);
23722375
void HandlePragmaDependency(Token &DependencyTok);
23732376
void HandlePragmaPushMacro(Token &Tok);
23742377
void HandlePragmaPopMacro(Token &Tok);

clang/include/clang/Lex/PreprocessorLexer.h

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,11 +14,13 @@
1414
#ifndef LLVM_CLANG_LEX_PREPROCESSORLEXER_H
1515
#define LLVM_CLANG_LEX_PREPROCESSORLEXER_H
1616

17+
#include "clang/Basic/SourceLocation.h"
18+
#include "clang/Lex/HeaderSearch.h"
1719
#include "clang/Lex/MultipleIncludeOpt.h"
1820
#include "clang/Lex/Token.h"
19-
#include "clang/Basic/SourceLocation.h"
2021
#include "llvm/ADT/ArrayRef.h"
2122
#include "llvm/ADT/SmallVector.h"
23+
#include "llvm/ADT/StringMap.h"
2224
#include <cassert>
2325

2426
namespace clang {
@@ -74,6 +76,13 @@ class PreprocessorLexer {
7476
/// we are currently in.
7577
SmallVector<PPConditionalInfo, 4> ConditionalStack;
7678

79+
struct IncludeInfo {
80+
const FileEntry *File;
81+
SourceLocation Location;
82+
};
83+
// A complete history of all the files included by the current file.
84+
llvm::StringMap<IncludeInfo> IncludeHistory;
85+
7786
PreprocessorLexer() : FID() {}
7887
PreprocessorLexer(Preprocessor *pp, FileID fid);
7988
virtual ~PreprocessorLexer() = default;
@@ -175,6 +184,15 @@ class PreprocessorLexer {
175184
ConditionalStack.clear();
176185
ConditionalStack.append(CL.begin(), CL.end());
177186
}
187+
188+
void addInclude(StringRef Filename, const FileEntry &File,
189+
SourceLocation Location) {
190+
IncludeHistory.insert({Filename, {&File, Location}});
191+
}
192+
193+
const llvm::StringMap<IncludeInfo> &getIncludeHistory() const {
194+
return IncludeHistory;
195+
}
178196
};
179197

180198
} // namespace clang

clang/lib/Lex/Lexer.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2811,11 +2811,11 @@ bool Lexer::LexEndOfFile(Token &Result, const char *CurPtr) {
28112811
ConditionalStack.pop_back();
28122812
}
28132813

2814+
SourceLocation EndLoc = getSourceLocation(BufferEnd);
28142815
// C99 5.1.1.2p2: If the file is non-empty and didn't end in a newline, issue
28152816
// a pedwarn.
28162817
if (CurPtr != BufferStart && (CurPtr[-1] != '\n' && CurPtr[-1] != '\r')) {
28172818
DiagnosticsEngine &Diags = PP->getDiagnostics();
2818-
SourceLocation EndLoc = getSourceLocation(BufferEnd);
28192819
unsigned DiagID;
28202820

28212821
if (LangOpts.CPlusPlus11) {
@@ -2838,7 +2838,7 @@ bool Lexer::LexEndOfFile(Token &Result, const char *CurPtr) {
28382838
BufferPtr = CurPtr;
28392839

28402840
// Finally, let the preprocessor handle this.
2841-
return PP->HandleEndOfFile(Result, isPragmaLexer());
2841+
return PP->HandleEndOfFile(Result, EndLoc, isPragmaLexer());
28422842
}
28432843

28442844
/// isNextPPTokenLParen - Return 1 if the next unexpanded token lexed from

clang/lib/Lex/PPDirectives.cpp

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2022,6 +2022,12 @@ Preprocessor::ImportAction Preprocessor::HandleHeaderIncludeOrImport(
20222022
IsFrameworkFound, IsImportDecl, IsMapped, LookupFrom, LookupFromFile,
20232023
LookupFilename, RelativePath, SearchPath, SuggestedModule, isAngled);
20242024

2025+
// Record the header's filename for later use.
2026+
if (File)
2027+
CurLexer->addInclude(
2028+
{FilenameTok.getLiteralData(), FilenameTok.getLength()},
2029+
File->getFileEntry(), FilenameLoc);
2030+
20252031
if (usingPCHWithThroughHeader() && SkippingUntilPCHThroughHeader) {
20262032
if (File && isPCHThroughHeader(&File->getFileEntry()))
20272033
SkippingUntilPCHThroughHeader = false;

clang/lib/Lex/PPLexerChange.cpp

Lines changed: 43 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
//===----------------------------------------------------------------------===//
1313

1414
#include "clang/Basic/FileManager.h"
15+
#include "clang/Basic/SourceLocation.h"
1516
#include "clang/Basic/SourceManager.h"
1617
#include "clang/Lex/HeaderSearch.h"
1718
#include "clang/Lex/LexDiagnostic.h"
@@ -22,6 +23,7 @@
2223
#include "llvm/Support/FileSystem.h"
2324
#include "llvm/Support/MemoryBufferRef.h"
2425
#include "llvm/Support/Path.h"
26+
2527
using namespace clang;
2628

2729
//===----------------------------------------------------------------------===//
@@ -299,10 +301,46 @@ void Preprocessor::diagnoseMissingHeaderInUmbrellaDir(const Module &Mod) {
299301
}
300302
}
301303

304+
void Preprocessor::ResolvePragmaIncludeInstead(
305+
const SourceLocation Location) const {
306+
assert(Location.isValid());
307+
if (CurLexer == nullptr)
308+
return;
309+
310+
if (SourceMgr.isInSystemHeader(Location))
311+
return;
312+
313+
for (const auto &Include : CurLexer->getIncludeHistory()) {
314+
StringRef Filename = Include.getKey();
315+
const PreprocessorLexer::IncludeInfo &Info = Include.getValue();
316+
ArrayRef<SmallString<32>> Aliases =
317+
HeaderInfo.getFileInfo(Info.File).Aliases.getArrayRef();
318+
319+
if (Aliases.empty())
320+
continue;
321+
322+
switch (Aliases.size()) {
323+
case 1:
324+
Diag(Info.Location, diag::err_pragma_include_instead_system_reserved)
325+
<< Filename << 0 << Aliases[0];
326+
continue;
327+
case 2:
328+
Diag(Info.Location, diag::err_pragma_include_instead_system_reserved)
329+
<< Filename << 1 << Aliases[0] << Aliases[1];
330+
continue;
331+
default: {
332+
Diag(Info.Location, diag::err_pragma_include_instead_system_reserved)
333+
<< Filename << 2 << ("{'" + llvm::join(Aliases, "', '") + "'}");
334+
}
335+
}
336+
}
337+
}
338+
302339
/// HandleEndOfFile - This callback is invoked when the lexer hits the end of
303340
/// the current file. This either returns the EOF token or pops a level off
304341
/// the include stack and keeps going.
305-
bool Preprocessor::HandleEndOfFile(Token &Result, bool isEndOfMacro) {
342+
bool Preprocessor::HandleEndOfFile(Token &Result, SourceLocation EndLoc,
343+
bool isEndOfMacro) {
306344
assert(!CurTokenLexer &&
307345
"Ending a file when currently in a macro!");
308346

@@ -372,6 +410,9 @@ bool Preprocessor::HandleEndOfFile(Token &Result, bool isEndOfMacro) {
372410
}
373411
}
374412

413+
if (EndLoc.isValid())
414+
ResolvePragmaIncludeInstead(EndLoc);
415+
375416
// Complain about reaching a true EOF within arc_cf_code_audited.
376417
// We don't want to complain about reaching the end of a macro
377418
// instantiation or a _Pragma.
@@ -560,7 +601,7 @@ bool Preprocessor::HandleEndOfTokenLexer(Token &Result) {
560601
TokenLexerCache[NumCachedTokenLexers++] = std::move(CurTokenLexer);
561602

562603
// Handle this like a #include file being popped off the stack.
563-
return HandleEndOfFile(Result, true);
604+
return HandleEndOfFile(Result, {}, true);
564605
}
565606

566607
/// RemoveTopOfLexerStack - Pop the current lexer/macro exp off the top of the

0 commit comments

Comments
 (0)