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,17 @@ def pp_pragma_sysheader_in_main_file : Warning< "#pragma system_header ignored in main file">, InGroup>; + +def pp_pragma_include_instead_not_sysheader : Error< + "'#pragma clang include_instead' cannot be used outside of system headers">, + ShowInSystemHeader; +def pp_pragma_include_instead_unexpected_token : Error< + "'#pragma clang include_instead' expects '%0' as its next token; got '%1' instead">, + ShowInSystemHeader; +def pp_pragma_include_instead_system_reserved : Error< + "header '%0' is an implementation detail; #include %select{'%2'|either '%2' or '%3'|one of %2}1 instead">, + 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,12 @@ #include "clang/Lex/ModuleMap.h" #include "llvm/ADT/ArrayRef.h" #include "llvm/ADT/DenseMap.h" +#include "llvm/ADT/SetVector.h" +#include "llvm/ADT/SmallSet.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 +113,14 @@ /// 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::SetVector, + llvm::SmallVector, 2>, + llvm::SmallSet, 2>> + Aliases; + HeaderFileInfo() : isImport(false), isPragmaOnce(false), DirInfo(SrcMgr::C_User), External(false), isModuleHeader(false), isCompilingModuleHeader(false), @@ -453,6 +464,10 @@ getFileInfo(File).DirInfo = SrcMgr::C_System; } + void AddFileAlias(const FileEntry *File, StringRef Alias) { + getFileInfo(File).Aliases.insert(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 @@ -1953,7 +1953,8 @@ /// This either returns the EOF token and returns true, or /// pops a level off the include stack and returns false, at which point the /// client should call lex again. - bool HandleEndOfFile(Token &Result, bool isEndOfMacro = false); + bool HandleEndOfFile(Token &Result, SourceLocation Loc, + bool isEndOfMacro = false); /// Callback invoked when the current TokenLexer hits the end of its /// token stream. @@ -2363,12 +2364,14 @@ // Pragmas. void HandlePragmaDirective(PragmaIntroducer Introducer); + void ResolvePragmaIncludeInstead(SourceLocation Location) const; public: void HandlePragmaOnce(Token &OnceTok); 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(StringRef Filename, const FileEntry &File, + SourceLocation Location) { + IncludeHistory.insert({Filename, {&File, Location}}); + } + + const llvm::StringMap &getIncludeHistory() const { + return IncludeHistory; + } }; } // namespace clang diff --git a/clang/lib/Lex/Lexer.cpp b/clang/lib/Lex/Lexer.cpp --- a/clang/lib/Lex/Lexer.cpp +++ b/clang/lib/Lex/Lexer.cpp @@ -2811,11 +2811,11 @@ ConditionalStack.pop_back(); } + SourceLocation EndLoc = getSourceLocation(BufferEnd); // C99 5.1.1.2p2: If the file is non-empty and didn't end in a newline, issue // a pedwarn. if (CurPtr != BufferStart && (CurPtr[-1] != '\n' && CurPtr[-1] != '\r')) { DiagnosticsEngine &Diags = PP->getDiagnostics(); - SourceLocation EndLoc = getSourceLocation(BufferEnd); unsigned DiagID; if (LangOpts.CPlusPlus11) { @@ -2838,7 +2838,7 @@ BufferPtr = CurPtr; // Finally, let the preprocessor handle this. - return PP->HandleEndOfFile(Result, isPragmaLexer()); + return PP->HandleEndOfFile(Result, EndLoc, isPragmaLexer()); } /// isNextPPTokenLParen - Return 1 if the next unexpanded token lexed from 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,12 @@ IsFrameworkFound, IsImportDecl, IsMapped, LookupFrom, LookupFromFile, LookupFilename, RelativePath, SearchPath, SuggestedModule, isAngled); + // Record the header's filename for later use. + if (File) + CurLexer->addInclude( + {FilenameTok.getLiteralData(), FilenameTok.getLength()}, + File->getFileEntry(), FilenameLoc); + 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 @@ -12,6 +12,7 @@ //===----------------------------------------------------------------------===// #include "clang/Basic/FileManager.h" +#include "clang/Basic/SourceLocation.h" #include "clang/Basic/SourceManager.h" #include "clang/Lex/HeaderSearch.h" #include "clang/Lex/LexDiagnostic.h" @@ -22,6 +23,7 @@ #include "llvm/Support/FileSystem.h" #include "llvm/Support/MemoryBufferRef.h" #include "llvm/Support/Path.h" + using namespace clang; //===----------------------------------------------------------------------===// @@ -299,10 +301,51 @@ } } +void Preprocessor::ResolvePragmaIncludeInstead( + const SourceLocation Location) const { + assert(Location.isValid()); + if (CurLexer == nullptr) + return; + + if (SourceMgr.isInSystemHeader(Location)) + return; + + for (const auto &Include : CurLexer->getIncludeHistory()) { + StringRef Filename = Include.getKey(); + const auto &IncludeInfo = Include.getValue(); + const HeaderFileInfo &Info = HeaderInfo.getFileInfo(IncludeInfo.File); + + 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: { + std::string Aliases = + "{'" + llvm::join(Info.Aliases.begin(), Info.Aliases.end(), "', '") + + "'}"; + Diag(IncludeInfo.Location, + diag::pp_pragma_include_instead_system_reserved) + << Filename << 2 << Aliases; + } + } + } +} + /// HandleEndOfFile - This callback is invoked when the lexer hits the end of /// the current file. This either returns the EOF token or pops a level off /// the include stack and keeps going. -bool Preprocessor::HandleEndOfFile(Token &Result, bool isEndOfMacro) { +bool Preprocessor::HandleEndOfFile(Token &Result, SourceLocation EndLoc, + bool isEndOfMacro) { assert(!CurTokenLexer && "Ending a file when currently in a macro!"); @@ -372,6 +415,9 @@ } } + if (EndLoc.isValid()) + ResolvePragmaIncludeInstead(EndLoc); + // Complain about reaching a true EOF within arc_cf_code_audited. // We don't want to complain about reaching the end of a macro // instantiation or a _Pragma. @@ -560,7 +606,7 @@ TokenLexerCache[NumCachedTokenLexers++] = std::move(CurTokenLexer); // Handle this like a #include file being popped off the stack. - return HandleEndOfFile(Result, true); + return HandleEndOfFile(Result, {}, true); } /// RemoveTopOfLexerStack - Pop the current lexer/macro exp off the top of the 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,43 +497,90 @@ 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) { Token FilenameTok; - if (LexHeaderName(FilenameTok, /*AllowConcatenation*/false)) - return; + if (PP.LexHeaderName(FilenameTok, /*AllowConcatenation*/ false)) + return llvm::None; // 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 llvm::None; } // 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 llvm::None; 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 llvm::None; // 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, diag::err_pp_file_not_found) << Filename; + return llvm::None; + } + + 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)) { + Diag(Tok, diag::pp_pragma_include_instead_unexpected_token) + << "(" << getSpelling(Tok); + return; + } + + Optional File; + llvm::Optional FilenameTok = + LexHeader(*this, File, SuppressIncludeNotFoundError); + if (!FilenameTok) + return; + + Lex(Tok); + if (Tok.isNot(tok::r_paren)) { + Diag(Tok, diag::pp_pragma_include_instead_unexpected_token) + << ")" << getSpelling(Tok); return; } + HeaderInfo.AddFileAlias( + TheLexer->getFileEntry(), + {FilenameTok->getLiteralData(), FilenameTok->getLength()}); +} + +/// HandlePragmaDependency - Handle \#pragma GCC dependency "foo" blah. +void Preprocessor::HandlePragmaDependency(Token &DependencyTok) { + Optional File; + llvm::Optional FilenameTok = + LexHeader(*this, File, SuppressIncludeNotFoundError); + if (!FilenameTok) + return; + const FileEntry *CurFile = getCurrentFileLexer()->getFileEntry(); // If this file is older than the file it depends on, emit a diagnostic. @@ -547,7 +596,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 +1071,18 @@ } }; +/// PragmaIncludeInsteadHandler - "\#pragma clang 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 +1995,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/bad-syntax.h b/clang/test/Preprocessor/Inputs/include_instead/bad-syntax.h new file mode 100644 --- /dev/null +++ b/clang/test/Preprocessor/Inputs/include_instead/bad-syntax.h @@ -0,0 +1,7 @@ +#pragma GCC system_header + +#pragma clang include_instead +// expected-error@-1{{'#pragma clang include_instead' expects '(' as its next token; got '<' instead}} + +#pragma clang include_instead(] +// expected-error@-1{{'#pragma clang include_instead' expects ')' as its next token; got ']' instead}} diff --git a/clang/test/Preprocessor/Inputs/include_instead/file-not-found.h b/clang/test/Preprocessor/Inputs/include_instead/file-not-found.h new file mode 100644 --- /dev/null +++ b/clang/test/Preprocessor/Inputs/include_instead/file-not-found.h @@ -0,0 +1,3 @@ +#pragma GCC system_header +#pragma clang include_instead() +// expected-error@-1{{'include_instead/does_not_exist.h' file not found}} diff --git a/clang/test/Preprocessor/Inputs/include_instead/non-system-header.h b/clang/test/Preprocessor/Inputs/include_instead/non-system-header.h new file mode 100644 --- /dev/null +++ b/clang/test/Preprocessor/Inputs/include_instead/non-system-header.h @@ -0,0 +1,2 @@ +#pragma clang include_instead() +// expected-error@-1{{'#pragma clang include_instead' cannot be used outside of system headers}} diff --git a/clang/test/Preprocessor/Inputs/include_instead/private-x.h b/clang/test/Preprocessor/Inputs/include_instead/private-x.h new file mode 100644 --- /dev/null +++ b/clang/test/Preprocessor/Inputs/include_instead/private-x.h @@ -0,0 +1,4 @@ +#include + +#pragma GCC system_header +#pragma clang include_instead() diff --git a/clang/test/Preprocessor/Inputs/include_instead/private1.h b/clang/test/Preprocessor/Inputs/include_instead/private1.h new file mode 100644 --- /dev/null +++ b/clang/test/Preprocessor/Inputs/include_instead/private1.h @@ -0,0 +1,2 @@ +#pragma GCC system_header +#pragma clang include_instead() diff --git a/clang/test/Preprocessor/Inputs/include_instead/private2.h b/clang/test/Preprocessor/Inputs/include_instead/private2.h new file mode 100644 --- /dev/null +++ b/clang/test/Preprocessor/Inputs/include_instead/private2.h @@ -0,0 +1,4 @@ +#pragma GCC system_header + +#pragma clang include_instead() +#pragma clang include_instead("include_instead/public-after.h") diff --git a/clang/test/Preprocessor/Inputs/include_instead/private3.h b/clang/test/Preprocessor/Inputs/include_instead/private3.h new file mode 100644 --- /dev/null +++ b/clang/test/Preprocessor/Inputs/include_instead/private3.h @@ -0,0 +1,5 @@ +#pragma GCC system_header + +#pragma clang include_instead() +#pragma clang include_instead() +#pragma clang include_instead("include_instead/public-before.h") diff --git a/clang/test/Preprocessor/Inputs/include_instead/public-after.h b/clang/test/Preprocessor/Inputs/include_instead/public-after.h new file mode 100644 --- /dev/null +++ b/clang/test/Preprocessor/Inputs/include_instead/public-after.h @@ -0,0 +1,2 @@ +#include +#pragma GCC system_header diff --git a/clang/test/Preprocessor/Inputs/include_instead/public-before.h b/clang/test/Preprocessor/Inputs/include_instead/public-before.h new file mode 100644 --- /dev/null +++ b/clang/test/Preprocessor/Inputs/include_instead/public-before.h @@ -0,0 +1,5 @@ +#pragma GCC system_header + +#include // no warning expected +#include // no warning expected +#include // no warning expected diff --git a/clang/test/Preprocessor/Inputs/include_instead/public-empty.h b/clang/test/Preprocessor/Inputs/include_instead/public-empty.h new file mode 100644 --- /dev/null +++ b/clang/test/Preprocessor/Inputs/include_instead/public-empty.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,16 @@ +// RUN: %clang_cc1 -fsyntax-only -verify -I %S/Inputs %s + +#include +#include + +#include +// expected-error@-1{{header '' is an implementation detail; #include '' instead}} + +#include "include_instead/private2.h" +// expected-error@-1{{header '"include_instead/private2.h"' is an implementation detail; #include either '' or '"include_instead/public-after.h"' instead}} + +#include +// expected-error@-1{{header '' is an implementation detail; #include one of {'', '', '"include_instead/public-before.h"'} instead}} + +#include +#include diff --git a/clang/test/Preprocessor/include_instead_file_not_found.cpp b/clang/test/Preprocessor/include_instead_file_not_found.cpp new file mode 100644 --- /dev/null +++ b/clang/test/Preprocessor/include_instead_file_not_found.cpp @@ -0,0 +1,2 @@ +// RUN: %clang_cc1 -fsyntax-only -verify -I %S/Inputs %s +#include