diff --git a/clang/include/clang/Basic/DiagnosticGroups.td b/clang/include/clang/Basic/DiagnosticGroups.td --- a/clang/include/clang/Basic/DiagnosticGroups.td +++ b/clang/include/clang/Basic/DiagnosticGroups.td @@ -693,8 +693,10 @@ def PragmaClangAttribute : DiagGroup<"pragma-clang-attribute">; def PragmaPackSuspiciousInclude : DiagGroup<"pragma-pack-suspicious-include">; def PragmaPack : DiagGroup<"pragma-pack", [PragmaPackSuspiciousInclude]>; +def PragmaIncludeInstead : DiagGroup<"pragma-include-instead">; def Pragmas : DiagGroup<"pragmas", [UnknownPragmas, IgnoredPragmas, - PragmaClangAttribute, PragmaPack]>; + PragmaClangAttribute, PragmaPack, + PragmaIncludeInstead]>; def UnknownWarningOption : DiagGroup<"unknown-warning-option">; def NSobjectAttribute : DiagGroup<"NSObject-attribute">; def NSConsumedMismatch : DiagGroup<"nsconsumed-mismatch">; diff --git a/clang/include/clang/Basic/DiagnosticLexKinds.td b/clang/include/clang/Basic/DiagnosticLexKinds.td --- a/clang/include/clang/Basic/DiagnosticLexKinds.td +++ b/clang/include/clang/Basic/DiagnosticLexKinds.td @@ -300,6 +300,22 @@ def pp_pragma_sysheader_in_main_file : Warning< "#pragma system_header ignored in main file">, InGroup>; +def pp_pragma_include_instead_not_sysheader : Warning< + "'#pragma include_instead' ignored outside of system headers">, + InGroup, + ShowInSystemHeader; +def pp_pragma_include_instead_unexpected_token : Warning< + "'#pragma include_instead' expects '%0' as its next token; got '%1' instead">, + InGroup, + ShowInSystemHeader; +def pp_pragma_include_instead_file_not_found : Warning< + "'%0' file not found">, + InGroup, + ShowInSystemHeader; +def pp_pragma_include_instead_system_reserved : Warning< + "header '%0' is an implementation detail; #include %select{'%2'|either '%2' or '%3'|one of %2}1 instead">, + InGroup, + ShowInSystemHeader; def pp_poisoning_existing_macro : Warning<"poisoning existing macro">; def pp_out_of_date_dependency : Warning< "current file is older than dependency %0">; diff --git a/clang/include/clang/Lex/HeaderSearch.h b/clang/include/clang/Lex/HeaderSearch.h --- a/clang/include/clang/Lex/HeaderSearch.h +++ b/clang/include/clang/Lex/HeaderSearch.h @@ -20,9 +20,10 @@ #include "clang/Lex/ModuleMap.h" #include "llvm/ADT/ArrayRef.h" #include "llvm/ADT/DenseMap.h" +#include "llvm/ADT/SmallString.h" #include "llvm/ADT/StringMap.h" -#include "llvm/ADT/StringSet.h" #include "llvm/ADT/StringRef.h" +#include "llvm/ADT/StringSet.h" #include "llvm/Support/Allocator.h" #include #include @@ -110,6 +111,11 @@ /// of the framework. StringRef Framework; + /// List of aliases that this header is known as. + /// Most headers should only have at most one alias, but a handful + /// have two. + llvm::SmallVector, 2> Aliases; + HeaderFileInfo() : isImport(false), isPragmaOnce(false), DirInfo(SrcMgr::C_User), External(false), isModuleHeader(false), isCompilingModuleHeader(false), @@ -453,6 +459,15 @@ getFileInfo(File).DirInfo = SrcMgr::C_System; } + void AddFileAlias(const FileEntry *File, StringRef Alias) { + llvm::SmallVector, 2> &Aliases = + getFileInfo(File).Aliases; + auto InsertionPoint = llvm::lower_bound(Aliases, Alias); + if (InsertionPoint != Aliases.end() && *InsertionPoint == Alias) + return; + Aliases.insert(InsertionPoint, Alias); + } + /// Mark the specified file as part of a module. void MarkFileModuleHeader(const FileEntry *FE, ModuleMap::ModuleHeaderRole Role, diff --git a/clang/include/clang/Lex/Preprocessor.h b/clang/include/clang/Lex/Preprocessor.h --- a/clang/include/clang/Lex/Preprocessor.h +++ b/clang/include/clang/Lex/Preprocessor.h @@ -2370,6 +2370,7 @@ void HandlePragmaMark(Token &MarkTok); void HandlePragmaPoison(); void HandlePragmaSystemHeader(Token &SysHeaderTok); + void HandlePragmaIncludeInstead(Token &Tok); void HandlePragmaDependency(Token &DependencyTok); void HandlePragmaPushMacro(Token &Tok); void HandlePragmaPopMacro(Token &Tok); diff --git a/clang/include/clang/Lex/PreprocessorLexer.h b/clang/include/clang/Lex/PreprocessorLexer.h --- a/clang/include/clang/Lex/PreprocessorLexer.h +++ b/clang/include/clang/Lex/PreprocessorLexer.h @@ -14,11 +14,13 @@ #ifndef LLVM_CLANG_LEX_PREPROCESSORLEXER_H #define LLVM_CLANG_LEX_PREPROCESSORLEXER_H +#include "clang/Basic/SourceLocation.h" +#include "clang/Lex/HeaderSearch.h" #include "clang/Lex/MultipleIncludeOpt.h" #include "clang/Lex/Token.h" -#include "clang/Basic/SourceLocation.h" #include "llvm/ADT/ArrayRef.h" #include "llvm/ADT/SmallVector.h" +#include "llvm/ADT/StringMap.h" #include namespace clang { @@ -74,6 +76,13 @@ /// we are currently in. SmallVector ConditionalStack; + struct IncludeInfo { + const FileEntry *File; + SourceLocation Location; + }; + // A complete history of all the files included by the current file. + llvm::StringMap IncludeHistory; + PreprocessorLexer() : FID() {} PreprocessorLexer(Preprocessor *pp, FileID fid); virtual ~PreprocessorLexer() = default; @@ -175,6 +184,15 @@ ConditionalStack.clear(); ConditionalStack.append(CL.begin(), CL.end()); } + + void addInclude(const StringRef Filename, const FileEntry &File, + const SourceLocation Location) { + IncludeHistory.insert({Filename, {&File, Location}}); + } + + const llvm::StringMap &getIncludeHistory() const { + return IncludeHistory; + } }; } // namespace clang diff --git a/clang/lib/Lex/PPDirectives.cpp b/clang/lib/Lex/PPDirectives.cpp --- a/clang/lib/Lex/PPDirectives.cpp +++ b/clang/lib/Lex/PPDirectives.cpp @@ -2022,6 +2022,11 @@ IsFrameworkFound, IsImportDecl, IsMapped, LookupFrom, LookupFromFile, LookupFilename, RelativePath, SearchPath, SuggestedModule, isAngled); + // Record the header's filename for later use + if (File) + CurLexer->addInclude(FilenameTok.getRawIdentifier(), File->getFileEntry(), + FilenameTok.getLocation()); + if (usingPCHWithThroughHeader() && SkippingUntilPCHThroughHeader) { if (File && isPCHThroughHeader(&File->getFileEntry())) SkippingUntilPCHThroughHeader = false; diff --git a/clang/lib/Lex/PPLexerChange.cpp b/clang/lib/Lex/PPLexerChange.cpp --- a/clang/lib/Lex/PPLexerChange.cpp +++ b/clang/lib/Lex/PPLexerChange.cpp @@ -22,6 +22,9 @@ #include "llvm/Support/FileSystem.h" #include "llvm/Support/MemoryBufferRef.h" #include "llvm/Support/Path.h" + +#include + using namespace clang; //===----------------------------------------------------------------------===// @@ -395,6 +398,48 @@ PragmaAssumeNonNullLoc = SourceLocation(); } + if (CurLexer != nullptr) { + auto IncludeHistory = CurLexer->getIncludeHistory(); + for (const auto &Include : IncludeHistory) { + const StringRef Filename = Include.getKey(); + const auto &IncludeInfo = Include.getValue(); + const HeaderFileInfo &Info = HeaderInfo.getFileInfo(IncludeInfo.File); + + if (SourceMgr.isInSystemHeader(IncludeInfo.Location)) + break; + + if (Info.Aliases.empty()) + continue; + + switch (Info.Aliases.size()) { + case 1: + Diag(IncludeInfo.Location, + diag::pp_pragma_include_instead_system_reserved) + << Filename << 0 << Info.Aliases[0]; + continue; + case 2: + Diag(IncludeInfo.Location, + diag::pp_pragma_include_instead_system_reserved) + << Filename << 1 << Info.Aliases[0] << Info.Aliases[1]; + continue; + default: { + auto Quote = [](StringRef S) -> std::string { + return ("'" + S + "'").str(); + }; + std::string Aliases = + std::accumulate(Info.Aliases.begin() + 1, Info.Aliases.end(), + Quote(Info.Aliases.front()), + [Quote](const StringRef X, const StringRef Y) { + return X.str() + ", " + Quote(Y); + }); + Diag(IncludeInfo.Location, + diag::pp_pragma_include_instead_system_reserved) + << Filename << 2 << ('{' + Aliases + '}'); + } + } + } + } + bool LeavingPCHThroughHeader = false; // If this is a #include'd file, pop it off the include stack and continue diff --git a/clang/lib/Lex/Pragma.cpp b/clang/lib/Lex/Pragma.cpp --- a/clang/lib/Lex/Pragma.cpp +++ b/clang/lib/Lex/Pragma.cpp @@ -13,6 +13,7 @@ #include "clang/Lex/Pragma.h" #include "clang/Basic/Diagnostic.h" +#include "clang/Basic/DiagnosticLex.h" #include "clang/Basic/FileManager.h" #include "clang/Basic/IdentifierTable.h" #include "clang/Basic/LLVM.h" @@ -35,11 +36,12 @@ #include "clang/Lex/TokenLexer.h" #include "llvm/ADT/ArrayRef.h" #include "llvm/ADT/DenseMap.h" +#include "llvm/ADT/Optional.h" #include "llvm/ADT/STLExtras.h" #include "llvm/ADT/SmallString.h" #include "llvm/ADT/SmallVector.h" -#include "llvm/ADT/StringSwitch.h" #include "llvm/ADT/StringRef.h" +#include "llvm/ADT/StringSwitch.h" #include "llvm/Support/Compiler.h" #include "llvm/Support/ErrorHandling.h" #include "llvm/Support/Timer.h" @@ -495,40 +497,94 @@ SrcMgr::C_System); } -/// HandlePragmaDependency - Handle \#pragma GCC dependency "foo" blah. -void Preprocessor::HandlePragmaDependency(Token &DependencyTok) { +static llvm::Optional LexHeader(Preprocessor &PP, + Optional &File, + bool SuppressIncludeNotFoundError, + bool FatalError) { Token FilenameTok; - if (LexHeaderName(FilenameTok, /*AllowConcatenation*/false)) - return; + if (PP.LexHeaderName(FilenameTok, /*AllowConcatenation*/ false)) + return {}; // If the next token wasn't a header-name, diagnose the error. if (FilenameTok.isNot(tok::header_name)) { - Diag(FilenameTok.getLocation(), diag::err_pp_expects_filename); - return; + PP.Diag(FilenameTok.getLocation(), diag::err_pp_expects_filename); + return {}; } // Reserve a buffer to get the spelling. SmallString<128> FilenameBuffer; bool Invalid = false; - StringRef Filename = getSpelling(FilenameTok, FilenameBuffer, &Invalid); + StringRef Filename = PP.getSpelling(FilenameTok, FilenameBuffer, &Invalid); if (Invalid) - return; + return {}; bool isAngled = - GetIncludeFilenameSpelling(FilenameTok.getLocation(), Filename); + PP.GetIncludeFilenameSpelling(FilenameTok.getLocation(), Filename); // If GetIncludeFilenameSpelling set the start ptr to null, there was an // error. if (Filename.empty()) - return; + return {}; // Search include directories for this file. const DirectoryLookup *CurDir; - Optional File = - LookupFile(FilenameTok.getLocation(), Filename, isAngled, nullptr, - nullptr, CurDir, nullptr, nullptr, nullptr, nullptr, nullptr); + File = PP.LookupFile(FilenameTok.getLocation(), Filename, isAngled, nullptr, + nullptr, CurDir, nullptr, nullptr, nullptr, nullptr, + nullptr); if (!File) { if (!SuppressIncludeNotFoundError) - Diag(FilenameTok, diag::err_pp_file_not_found) << Filename; + PP.Diag(FilenameTok, FatalError + ? diag::err_pp_file_not_found + : diag::pp_pragma_include_instead_file_not_found) + << Filename; + return {}; + } + + return FilenameTok; +} + +/// HandlePragmaIncludeInstead - Handle \#pragma clang include_instead(header). +void Preprocessor::HandlePragmaIncludeInstead(Token &Tok) { + // Get the current file lexer we're looking at. Ignore _Pragma 'files' etc. + PreprocessorLexer *TheLexer = getCurrentFileLexer(); + + if (!SourceMgr.isInSystemHeader(Tok.getLocation())) { + Diag(Tok, diag::pp_pragma_include_instead_not_sysheader); + return; + } + + Lex(Tok); + if (Tok.isNot(tok::l_paren)) { + const std::string spelling = getSpelling(Tok); + Diag(Tok, diag::pp_pragma_include_instead_unexpected_token) + << "(" << (spelling == "\n" ? "\\n" : spelling); + return; + } + + Optional File; + llvm::Optional FilenameTok = LexHeader( + *this, File, SuppressIncludeNotFoundError, /*FatalError=*/false); + if (!FilenameTok) { + return; + } + + Lex(Tok); + if (Tok.isNot(tok::r_paren)) { + const std::string spelling = getSpelling(Tok); + Diag(Tok, diag::pp_pragma_include_instead_unexpected_token) + << ")" << spelling; + return; + } + + HeaderInfo.AddFileAlias(TheLexer->getFileEntry(), + FilenameTok->getRawIdentifier()); +} + +/// HandlePragmaDependency - Handle \#pragma GCC dependency "foo" blah. +void Preprocessor::HandlePragmaDependency(Token &DependencyTok) { + Optional File; + llvm::Optional FilenameTok = + LexHeader(*this, File, SuppressIncludeNotFoundError, /*FatalError=*/true); + if (!FilenameTok) { return; } @@ -547,7 +603,7 @@ // Remove the trailing ' ' if present. if (!Message.empty()) Message.erase(Message.end()-1); - Diag(FilenameTok, diag::pp_out_of_date_dependency) << Message; + Diag(*FilenameTok, diag::pp_out_of_date_dependency) << Message; } } @@ -1022,6 +1078,18 @@ } }; +/// PragmaIncludeInsteadHandler - "\#pragma include_instead(header)" marks the +/// current file as non-includable if the including header is not a system +/// header. +struct PragmaIncludeInsteadHandler : public PragmaHandler { + PragmaIncludeInsteadHandler() : PragmaHandler("include_instead") {} + + void HandlePragma(Preprocessor &PP, PragmaIntroducer Introducer, + Token &IIToken) override { + PP.HandlePragmaIncludeInstead(IIToken); + } +}; + struct PragmaDependencyHandler : public PragmaHandler { PragmaDependencyHandler() : PragmaHandler("dependency") {} @@ -1934,6 +2002,7 @@ // #pragma clang ... AddPragmaHandler("clang", new PragmaPoisonHandler()); AddPragmaHandler("clang", new PragmaSystemHeaderHandler()); + AddPragmaHandler("clang", new PragmaIncludeInsteadHandler()); AddPragmaHandler("clang", new PragmaDebugHandler()); AddPragmaHandler("clang", new PragmaDependencyHandler()); AddPragmaHandler("clang", new PragmaDiagnosticHandler("clang")); diff --git a/clang/test/Preprocessor/Inputs/include_instead/include_instead-bad-syntax.h b/clang/test/Preprocessor/Inputs/include_instead/include_instead-bad-syntax.h new file mode 100644 --- /dev/null +++ b/clang/test/Preprocessor/Inputs/include_instead/include_instead-bad-syntax.h @@ -0,0 +1,10 @@ +#pragma GCC system_header + +#pragma clang include_instead +// expected-warning@-1{{'#pragma include_instead' expects '(' as its next token; got '<' instead}} + +#pragma clang include_instead(] +// expected-warning@-1{{'#pragma include_instead' expects ')' as its next token; got ']' instead}} + +#pragma clang include_instead() +// expected-warning@-1{{'include_instead_does_not_exist.h' file not found}} diff --git a/clang/test/Preprocessor/Inputs/include_instead/include_instead-non-system-header.h b/clang/test/Preprocessor/Inputs/include_instead/include_instead-non-system-header.h new file mode 100644 --- /dev/null +++ b/clang/test/Preprocessor/Inputs/include_instead/include_instead-non-system-header.h @@ -0,0 +1,2 @@ +#pragma clang include_instead() +// expected-warning@-1{{'#pragma include_instead' ignored outside of system headers}} diff --git a/clang/test/Preprocessor/Inputs/include_instead/include_instead-nowarn.h b/clang/test/Preprocessor/Inputs/include_instead/include_instead-nowarn.h new file mode 100644 --- /dev/null +++ b/clang/test/Preprocessor/Inputs/include_instead/include_instead-nowarn.h @@ -0,0 +1,3 @@ +#pragma GCC system_header + +#include // no warning expected diff --git a/clang/test/Preprocessor/Inputs/include_instead/include_instead-warns-three-alternatives.h b/clang/test/Preprocessor/Inputs/include_instead/include_instead-warns-three-alternatives.h new file mode 100644 --- /dev/null +++ b/clang/test/Preprocessor/Inputs/include_instead/include_instead-warns-three-alternatives.h @@ -0,0 +1,5 @@ +#pragma GCC system_header + +#pragma clang include_instead() +#pragma clang include_instead("include_instead-nowarn.h") +#pragma clang include_instead("include_instead-warns-two-alternatives.h") diff --git a/clang/test/Preprocessor/Inputs/include_instead/include_instead-warns-two-alternatives.h b/clang/test/Preprocessor/Inputs/include_instead/include_instead-warns-two-alternatives.h new file mode 100644 --- /dev/null +++ b/clang/test/Preprocessor/Inputs/include_instead/include_instead-warns-two-alternatives.h @@ -0,0 +1,4 @@ +#pragma GCC system_header + +#pragma clang include_instead() +#pragma clang include_instead("include_instead-nowarn.h") diff --git a/clang/test/Preprocessor/Inputs/include_instead/include_instead-warns.h b/clang/test/Preprocessor/Inputs/include_instead/include_instead-warns.h new file mode 100644 --- /dev/null +++ b/clang/test/Preprocessor/Inputs/include_instead/include_instead-warns.h @@ -0,0 +1,3 @@ +#pragma GCC system_header + +#pragma clang include_instead() diff --git a/clang/test/Preprocessor/Inputs/include_instead/malloc.h b/clang/test/Preprocessor/Inputs/include_instead/malloc.h new file mode 100644 --- /dev/null +++ b/clang/test/Preprocessor/Inputs/include_instead/malloc.h @@ -0,0 +1 @@ +// This file simply needs to exist. diff --git a/clang/test/Preprocessor/include_instead.cpp b/clang/test/Preprocessor/include_instead.cpp new file mode 100644 --- /dev/null +++ b/clang/test/Preprocessor/include_instead.cpp @@ -0,0 +1,15 @@ +// RUN: %clang_cc1 -fsyntax-only -verify -I %S/Inputs/include_instead %s + +#include +#include + +#include +// expected-warning@-1{{header '' is an implementation detail; #include '' instead}} + +#include "include_instead-warns-two-alternatives.h" +// expected-warning@-1{{header '"include_instead-warns-two-alternatives.h"' is an implementation detail; #include either '"include_instead-nowarn.h"' or '' instead}} + +#include +// expected-warning@-1{{header '' is an implementation detail; #include one of {'"include_instead-nowarn.h"', '"include_instead-warns-two-alternatives.h"', ''} instead}} + +#include