diff --git a/clang/docs/LanguageExtensions.rst b/clang/docs/LanguageExtensions.rst --- a/clang/docs/LanguageExtensions.rst +++ b/clang/docs/LanguageExtensions.rst @@ -3906,6 +3906,29 @@ ``#pragma GCC warning`` because the warning can be controlled with ``-Wdeprecated``. +Header Unsafe Macros +==================== + +Clang supports the pragma ``#pragma clang header_unsafe``, which can be used to +mark macros as unsafe to use in headers. This can be valuable when providing +headers with ABI stability requirements. For example: + +.. code-block:: c + + #define TARGET_ARM 1 + #pragma clang header_unsafe(TARGET_ARM, "") + + /// Foo.h + struct Foo { + #if TARGET_ARM // warning: TARGET_ARM is marked unsafe in headers: + uint32_t X; + #else + uint64_t X; + #endif + }; + +This warning is controlled by ``-Wpedantic-macros``. + Extended Integer Types ====================== 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 @@ -1305,3 +1305,7 @@ def RTTI : DiagGroup<"rtti">; def OpenCLCoreFeaturesDiagGroup : DiagGroup<"pedantic-core-features">; + +// Warnings and extensions to make preprocessor macro usage pedantic +def PedanticMacros : DiagGroup<"pedantic-macros", + [DeprecatedPragma, MacroRedefined, BuiltinMacroRedefined]>; 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 @@ -524,6 +524,12 @@ ExtWarn<"macro %0 has been marked as deprecated%select{|: %2}1">, InGroup; +// - #pragma clang header_unsafe(...) +def warn_pragma_header_unsafe_macro_use : + ExtWarn<"macro %0 has been marked as unsafe for use in headers" + "%select{|: %2}1">, + InGroup; + // - #pragma execution_character_set(...) def warn_pragma_exec_charset_expected : ExtWarn<"#pragma execution_character_set expected '%0'">, diff --git a/clang/include/clang/Basic/IdentifierTable.h b/clang/include/clang/Basic/IdentifierTable.h --- a/clang/include/clang/Basic/IdentifierTable.h +++ b/clang/include/clang/Basic/IdentifierTable.h @@ -124,7 +124,10 @@ // True if this is a deprecated macro unsigned IsDeprecatedMacro : 1; - // 24 bits left in a 64-bit word. + // True if this macro is unsafe in headers + unsigned IsHeaderUnsafe : 1; + + // 23 bits left in a 64-bit word. // Managed by the language front-end. void *FETokenInfo = nullptr; @@ -138,7 +141,7 @@ NeedsHandleIdentifier(false), IsFromAST(false), ChangedAfterLoad(false), FEChangedAfterLoad(false), RevertedTokenID(false), OutOfDate(false), IsModulesImport(false), IsMangledOpenMPVariantName(false), - IsDeprecatedMacro(false) {} + IsDeprecatedMacro(false), IsHeaderUnsafe(false) {} public: IdentifierInfo(const IdentifierInfo &) = delete; @@ -186,8 +189,11 @@ NeedsHandleIdentifier = true; HadMacro = true; } else { + // Since calling the setters of these calls recompute, just set them + // manually to avoid calling recompute a bunch of times. + IsDeprecatedMacro = false; + IsHeaderUnsafe = false; RecomputeNeedsHandleIdentifier(); - setIsDeprecatedMacro(false); } } /// Returns true if this identifier was \#defined to some value at any @@ -209,6 +215,18 @@ RecomputeNeedsHandleIdentifier(); } + bool isHeaderUnsafe() const { return IsHeaderUnsafe; } + + void setIsHeaderUnsafe(bool Val) { + if (IsHeaderUnsafe == Val) + return; + IsHeaderUnsafe = Val; + if (Val) + NeedsHandleIdentifier = true; + else + RecomputeNeedsHandleIdentifier(); + } + /// If this is a source-language token (e.g. 'for'), this API /// can be used to cause the lexer to map identifiers to source-language /// tokens. 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 @@ -794,6 +794,9 @@ /// Deprecation messages for macros provided in #pragma clang deprecated llvm::DenseMap MacroDeprecationMsgs; + /// Usage warning for macros marked by #pragma clang header_unsafe + llvm::DenseMap HeaderUnsafeMacroMsgs; + /// A "freelist" of MacroArg objects that can be /// reused for quick allocation. MacroArgs *MacroArgCache = nullptr; @@ -2406,6 +2409,17 @@ return MsgEntry->second; } + void addHeaderUnsafeMsg(const IdentifierInfo *II, std::string Msg) { + HeaderUnsafeMacroMsgs.insert(std::make_pair(II, Msg)); + } + + llvm::Optional getHeaderUnsafeMsg(const IdentifierInfo *II) { + auto MsgEntry = HeaderUnsafeMacroMsgs.find(II); + if (MsgEntry == HeaderUnsafeMacroMsgs.end()) + return llvm::None; + return MsgEntry->second; + } + void emitMacroExpansionWarnings(const Token &Identifier); private: 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 @@ -1911,6 +1911,45 @@ } }; +/// This handles parsing pragmas that take a macro name and optional message +static IdentifierInfo *HandleMacroAnnotationPragma(Preprocessor &PP, Token &Tok, + const char *Pragma, + std::string &MessageString) { + std::string Macro; + + PP.Lex(Tok); + if (Tok.isNot(tok::l_paren)) { + PP.Diag(Tok, diag::err_expected) << "("; + return nullptr; + } + + PP.LexUnexpandedToken(Tok); + if (!Tok.is(tok::identifier)) { + PP.Diag(Tok, diag::err_expected) << tok::identifier; + return nullptr; + } + IdentifierInfo *II = Tok.getIdentifierInfo(); + + if (!II->hasMacroDefinition()) { + PP.Diag(Tok, diag::err_pp_visibility_non_macro) << II->getName(); + return nullptr; + } + + PP.Lex(Tok); + if (Tok.is(tok::comma)) { + PP.Lex(Tok); + if (!PP.FinishLexStringLiteral(Tok, MessageString, Pragma, + /*AllowMacroExpansion=*/true)) + return nullptr; + } + + if (Tok.isNot(tok::r_paren)) { + PP.Diag(Tok, diag::err_expected) << ")"; + return nullptr; + } + return II; +} + /// "\#pragma clang deprecated(...)" /// /// The syntax is @@ -1922,43 +1961,36 @@ void HandlePragma(Preprocessor &PP, PragmaIntroducer Introducer, Token &Tok) override { - std::string Macro, MessageString; - - PP.Lex(Tok); - if (Tok.isNot(tok::l_paren)) { - PP.Diag(Tok, diag::err_expected) << "("; - return; - } + std::string MessageString; - PP.LexUnexpandedToken(Tok); - if (!Tok.is(tok::identifier)) { - PP.Diag(Tok, diag::err_expected) << tok::identifier; - return; + if (IdentifierInfo *II = HandleMacroAnnotationPragma( + PP, Tok, "#pragma clang deprecated", MessageString)) { + II->setIsDeprecatedMacro(true); + if (!MessageString.empty()) + PP.addMacroDeprecationMsg(II, std::move(MessageString)); } - IdentifierInfo *II = Tok.getIdentifierInfo(); + } +}; - if (!II->hasMacroDefinition()) { - PP.Diag(Tok, diag::err_pp_visibility_non_macro) << II->getName(); - return; - } +/// "\#pragma clang header_unsafe(...)" +/// +/// The syntax is +/// \code +/// #pragma clang header_unsafe(MACRO_NAME [, Message]) +/// \endcode +struct PragmaHeaderUnsafeHandler : public PragmaHandler { + PragmaHeaderUnsafeHandler() : PragmaHandler("header_unsafe") {} - PP.Lex(Tok); - if (Tok.is(tok::comma)) { - PP.Lex(Tok); - if (!PP.FinishLexStringLiteral(Tok, MessageString, - "#pragma clang deprecated", - /*AllowMacroExpansion=*/true)) - return; - } + void HandlePragma(Preprocessor &PP, PragmaIntroducer Introducer, + Token &Tok) override { + std::string MessageString; - if (Tok.isNot(tok::r_paren)) { - PP.Diag(Tok, diag::err_expected) << ")"; - return; + if (IdentifierInfo *II = HandleMacroAnnotationPragma( + PP, Tok, "#pragma clang header_unsafe", MessageString)) { + II->setIsHeaderUnsafe(true); + if (!MessageString.empty()) + PP.addHeaderUnsafeMsg(II, std::move(MessageString)); } - - II->setIsDeprecatedMacro(true); - if (!MessageString.empty()) - PP.addMacroDeprecationMsg(II, std::move(MessageString)); } }; @@ -1991,6 +2023,7 @@ AddPragmaHandler("clang", new PragmaARCCFCodeAuditedHandler()); AddPragmaHandler("clang", new PragmaAssumeNonNullHandler()); AddPragmaHandler("clang", new PragmaDeprecatedHandler()); + AddPragmaHandler("clang", new PragmaHeaderUnsafeHandler()); // #pragma clang module ... auto *ModuleHandler = new PragmaNamespace("module"); diff --git a/clang/lib/Lex/Preprocessor.cpp b/clang/lib/Lex/Preprocessor.cpp --- a/clang/lib/Lex/Preprocessor.cpp +++ b/clang/lib/Lex/Preprocessor.cpp @@ -1423,6 +1423,17 @@ Diag(Identifier, diag::warn_pragma_deprecated_macro_use) << Identifier.getIdentifierInfo() << 1 << *DepMsg; } + + if (Identifier.getIdentifierInfo()->isHeaderUnsafe() && + !SourceMgr.isInMainFile(Identifier.getLocation())) { + auto DepMsg = getHeaderUnsafeMsg(Identifier.getIdentifierInfo()); + if (!DepMsg) + Diag(Identifier, diag::warn_pragma_header_unsafe_macro_use) + << Identifier.getIdentifierInfo() << 0; + else + Diag(Identifier, diag::warn_pragma_header_unsafe_macro_use) + << Identifier.getIdentifierInfo() << 1 << *DepMsg; + } } ModuleLoader::~ModuleLoader() = default; diff --git a/clang/test/Lexer/Inputs/unsafe-macro-2.h b/clang/test/Lexer/Inputs/unsafe-macro-2.h new file mode 100644 --- /dev/null +++ b/clang/test/Lexer/Inputs/unsafe-macro-2.h @@ -0,0 +1,70 @@ +// expected-warning@+1{{macro 'UNSAFE_MACRO' has been marked as unsafe for use in headers: Don't use this!}} +#if UNSAFE_MACRO +#endif + +// expected-warning@+1{{macro 'UNSAFE_MACRO' has been marked as unsafe for use in headers: Don't use this!}} +#if defined(UNSAFE_MACRO) +#endif + +// expected-warning@+1{{macro 'UNSAFE_MACRO' has been marked as unsafe for use in headers: Don't use this!}} +#ifdef UNSAFE_MACRO +#endif + +// expected-warning@+1{{macro 'UNSAFE_MACRO' has been marked as unsafe for use in headers: Don't use this!}} +#ifndef UNSAFE_MACRO +#endif + +// expected-warning@+1{{macro 'UNSAFE_MACRO' has been marked as unsafe for use in headers: Don't use this!}} +const int x = UNSAFE_MACRO; + +// expected-warning@+1{{macro 'UNSAFE_MACRO_2' has been marked as unsafe for use in headers}} +const int y = UNSAFE_MACRO_2; + +// not-expected-warning@+1{{macro 'UNSAFE_MACRO_2' has been marked as unsafe for use in headers}} +#undef UNSAFE_MACRO_2 +// not-expected-warning@+1{{macro 'UNSAFE_MACRO_2' has been marked as unsafe for use in headers}} +#define UNSAFE_MACRO_2 2 + +// not-expected-warning@+1{{macro 'UNSAFE_MACRO_2' has been marked as unsafe for use in headers}} +const int z = UNSAFE_MACRO_2; + + +// Test that we diagnose on #elif. +#if 0 +#elif UNSAFE_MACRO +// expected-warning@-1{{macro 'UNSAFE_MACRO' has been marked as unsafe for use in headers: Don't use this!}} +#endif + + +// Test that we diagnose on #elifdef. +#ifdef baz +#elifdef UNSAFE_MACRO +// expected-warning@-1{{macro 'UNSAFE_MACRO' has been marked as unsafe for use in headers: Don't use this!}} +#endif + +// Test that we diagnose on #elifndef. +#ifdef baz +#elifndef UNSAFE_MACRO +#endif +// expected-warning@-2{{macro 'UNSAFE_MACRO' has been marked as unsafe for use in headers: Don't use this!}} + +// FIXME: These cases are currently not handled because clang doesn't expand +// conditions on skipped #elif* blocks. See the FIXME notes in +// Preprocessor::SkipExcludedConditionalBlock. + +#define frobble + +#ifdef frobble +// not-expected-warning@+1{{macro 'UNSAFE_MACRO' has been marked as unsafe for use in headers: Don't use this!}} +#elifndef UNSAFE_MACRO +#endif + +#ifdef frobble +// not-expected-warning@+1{{macro 'UNSAFE_MACRO' has been marked as unsafe for use in headers: Don't use this!}} +#elifdef UNSAFE_MACRO +#endif + +#if 1 +// not-expected-warning@+1{{macro 'UNSAFE_MACRO' has been marked as unsafe for use in headers: Don't use this!}} +#elif UNSAFE_MACRO +#endif diff --git a/clang/test/Lexer/Inputs/unsafe-macro.h b/clang/test/Lexer/Inputs/unsafe-macro.h new file mode 100644 --- /dev/null +++ b/clang/test/Lexer/Inputs/unsafe-macro.h @@ -0,0 +1,18 @@ +// expected-error@+1{{expected (}} +#pragma clang header_unsafe + +// expected-error@+1{{expected identifier}} +#pragma clang header_unsafe(4 + +// expected-error@+1{{no macro named foo}} +#pragma clang header_unsafe(foo) + + +#define UNSAFE_MACRO 1 +#pragma clang header_unsafe(UNSAFE_MACRO, "Don't use this!") + +#define UNSAFE_MACRO_2 2 +#pragma clang header_unsafe(UNSAFE_MACRO_2) + +// expected-error@+1{{expected )}} +#pragma clang deprecated(UNSAFE_MACRO diff --git a/clang/test/Lexer/unsafe-macro.c b/clang/test/Lexer/unsafe-macro.c new file mode 100644 --- /dev/null +++ b/clang/test/Lexer/unsafe-macro.c @@ -0,0 +1,7 @@ +// RUN: %clang_cc1 -Wpedantic-macros %s -fsyntax-only -verify +#include "Inputs/unsafe-macro.h" +#include "Inputs/unsafe-macro-2.h" + +// not-expected-warning@+1{{macro 'UNSAFE_MACRO' has been marked as unsafe for use in headers: Don't use this!}} +#if UNSAFE_MACRO +#endif