diff --git a/clang/docs/LanguageExtensions.rst b/clang/docs/LanguageExtensions.rst --- a/clang/docs/LanguageExtensions.rst +++ b/clang/docs/LanguageExtensions.rst @@ -3931,6 +3931,40 @@ ``#pragma GCC warning`` because the warning can be controlled with ``-Wdeprecated``. +Restricted Expansion Macros +=========================== + +Clang supports the pragma ``#pragma clang restrict_expansion``, which can be +used restrict macro expansion in headers. This can be valuable when providing +headers with ABI stability requirements. Any expansion of the annotated macro +processed by the preprocessor after the ``#pragma`` annotation will log a +warning. Redefining the macro or undefining the macro will not be diagnosed, nor +will expansion of the macro within the main source file. For example: + +.. code-block:: c + + #define TARGET_ARM 1 + #pragma clang restrict_expansion(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 + }; + + /// main.c + #include "foo.h" + #if TARGET_ARM // No warning in main source file + X_TYPE uint32_t + #else + X_TYPE uint64_t + #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 @@ -647,6 +647,7 @@ def KeywordAsMacro : DiagGroup<"keyword-macro">; def ReservedIdAsMacro : DiagGroup<"reserved-macro-identifier">; def ReservedIdAsMacroAlias : DiagGroup<"reserved-id-macro", [ReservedIdAsMacro]>; +def RestrictExpansionMacro : DiagGroup<"restrict-expansion">; // Just silence warnings about -Wstrict-aliasing for now. def : DiagGroup<"strict-aliasing=0">; @@ -1311,3 +1312,10 @@ 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, + RestrictExpansionMacro]>; 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 @@ -528,6 +528,16 @@ ExtWarn<"macro %0 has been marked as deprecated%select{|: %2}1">, InGroup; +// - #pragma clang restrict_expansion(...) +def warn_pragma_restrict_expansion_macro_use : + ExtWarn<"macro %0 has been marked as unsafe for use in headers" + "%select{|: %2}1">, + InGroup; + +// - Note for macro annotations. +def note_pp_macro_annotation : + Note<"macro marked '%select{deprecated|restrict_expansion}0' here">; + // - #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 @@ -121,10 +121,13 @@ // True if this is a mangled OpenMP variant name. unsigned IsMangledOpenMPVariantName : 1; - // True if this is a deprecated macro + // 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 IsRestrictExpansion : 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), IsRestrictExpansion(false) {} public: IdentifierInfo(const IdentifierInfo &) = delete; @@ -186,8 +189,11 @@ NeedsHandleIdentifier = true; HadMacro = true; } else { + // Because calling the setters of these calls recomputes, just set them + // manually to avoid recomputing a bunch of times. + IsDeprecatedMacro = false; + IsRestrictExpansion = false; RecomputeNeedsHandleIdentifier(); - setIsDeprecatedMacro(false); } } /// Returns true if this identifier was \#defined to some value at any @@ -209,6 +215,18 @@ RecomputeNeedsHandleIdentifier(); } + bool isRestrictExpansion() const { return IsRestrictExpansion; } + + void setIsRestrictExpansion(bool Val) { + if (IsRestrictExpansion == Val) + return; + IsRestrictExpansion = 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 @@ -15,6 +15,7 @@ #define LLVM_CLANG_LEX_PREPROCESSOR_H #include "clang/Basic/Diagnostic.h" +#include "clang/Basic/DiagnosticIDs.h" #include "clang/Basic/IdentifierTable.h" #include "clang/Basic/LLVM.h" #include "clang/Basic/LangOptions.h" @@ -791,9 +792,19 @@ using WarnUnusedMacroLocsTy = llvm::SmallDenseSet; WarnUnusedMacroLocsTy WarnUnusedMacroLocs; - /// Deprecation messages for macros provided in #pragma clang deprecated + /// This is a pair of an optional message and source location used for pragmas + /// that annotate macros like pragma clang restrict_expansion and pragma clang + /// deprecated. This pair stores the optional message and the location of the + /// annotation pragma for use producing diagnostics and notes. + using MsgLocationPair = std::pair; + + /// Deprecation messages for macros provided in #pragma clang deprecated. llvm::DenseMap MacroDeprecationMsgs; + /// Usage warning for macros marked by #pragma clang restrict_expansion. + llvm::DenseMap + RestrictExpansionMacroMsgs; + /// A "freelist" of MacroArg objects that can be /// reused for quick allocation. MacroArgs *MacroArgCache = nullptr; @@ -2409,9 +2420,29 @@ return MsgEntry->second; } - void emitMacroExpansionWarnings(const Token &Identifier); + void addRestrictExpansionMsg(const IdentifierInfo *II, std::string Msg, + SourceLocation AnnotationLoc) { + RestrictExpansionMacroMsgs.insert( + std::make_pair(II, std::make_pair(std::move(Msg), AnnotationLoc))); + } + + MsgLocationPair getRestrictExpansionMsg(const IdentifierInfo *II) { + return RestrictExpansionMacroMsgs.find(II)->second; + } + + void emitMacroExpansionWarnings(const Token &Identifier) { + if (Identifier.getIdentifierInfo()->isDeprecatedMacro()) + emitMacroDeprecationWarning(Identifier); + + if (Identifier.getIdentifierInfo()->isRestrictExpansion() && + !SourceMgr.isInMainFile(Identifier.getLocation())) + emitMacroUnsafeHeaderWarning(Identifier); + } private: + void emitMacroDeprecationWarning(const Token &Identifier); + void emitMacroUnsafeHeaderWarning(const Token &Identifier); + Optional getSkippedRangeForExcludedConditionalBlock(SourceLocation HashLoc); 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 @@ -1970,6 +1970,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 @@ -1981,43 +2020,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 restrict_expansion(...)" +/// +/// The syntax is +/// \code +/// #pragma clang restrict_expansion(MACRO_NAME [, Message]) +/// \endcode +struct PragmaRestrictExpansionHandler : public PragmaHandler { + PragmaRestrictExpansionHandler() : PragmaHandler("restrict_expansion") {} - 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 restrict_expansion", MessageString)) { + II->setIsRestrictExpansion(true); + PP.addRestrictExpansionMsg(II, std::move(MessageString), + Tok.getLocation()); } - - II->setIsDeprecatedMacro(true); - if (!MessageString.empty()) - PP.addMacroDeprecationMsg(II, std::move(MessageString)); } }; @@ -2051,6 +2083,7 @@ AddPragmaHandler("clang", new PragmaARCCFCodeAuditedHandler()); AddPragmaHandler("clang", new PragmaAssumeNonNullHandler()); AddPragmaHandler("clang", new PragmaDeprecatedHandler()); + AddPragmaHandler("clang", new PragmaRestrictExpansionHandler()); // #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 @@ -1413,16 +1413,25 @@ return true; } -void Preprocessor::emitMacroExpansionWarnings(const Token &Identifier) { - if (Identifier.getIdentifierInfo()->isDeprecatedMacro()) { - auto DepMsg = getMacroDeprecationMsg(Identifier.getIdentifierInfo()); - if (!DepMsg) - Diag(Identifier, diag::warn_pragma_deprecated_macro_use) - << Identifier.getIdentifierInfo() << 0; - else - Diag(Identifier, diag::warn_pragma_deprecated_macro_use) - << Identifier.getIdentifierInfo() << 1 << *DepMsg; - } +void Preprocessor::emitMacroDeprecationWarning(const Token &Identifier) { + auto DepMsg = getMacroDeprecationMsg(Identifier.getIdentifierInfo()); + if (!DepMsg) + Diag(Identifier, diag::warn_pragma_deprecated_macro_use) + << Identifier.getIdentifierInfo() << 0; + else + Diag(Identifier, diag::warn_pragma_deprecated_macro_use) + << Identifier.getIdentifierInfo() << 1 << *DepMsg; +} + +void Preprocessor::emitMacroUnsafeHeaderWarning(const Token &Identifier) { + auto DepMsg = getRestrictExpansionMsg(Identifier.getIdentifierInfo()); + if (DepMsg.first.empty()) + Diag(Identifier, diag::warn_pragma_restrict_expansion_macro_use) + << Identifier.getIdentifierInfo() << 0; + else + Diag(Identifier, diag::warn_pragma_restrict_expansion_macro_use) + << Identifier.getIdentifierInfo() << 1 << DepMsg.first; + Diag(DepMsg.second, diag::note_pp_macro_annotation) << 1; } ModuleLoader::~ModuleLoader() = default; diff --git a/clang/test/Lexer/Inputs/pedantic-macro-interplay.h b/clang/test/Lexer/Inputs/pedantic-macro-interplay.h new file mode 100644 --- /dev/null +++ b/clang/test/Lexer/Inputs/pedantic-macro-interplay.h @@ -0,0 +1,9 @@ +#define UNSAFE_MACRO 1 +#pragma clang restrict_expansion(UNSAFE_MACRO, "Don't use this!") +// not-expected-warning@+1{{macro 'UNSAFE_MACRO' has been marked as unsafe for use in headers: Don't use this!}} +#pragma clang deprecated(UNSAFE_MACRO, "Don't use this!") + +#define UNSAFE_MACRO_2 1 +#pragma clang deprecated(UNSAFE_MACRO_2, "Don't use this!") +// not-expected-warning@+1{{macro 'UNSAFE_MACRO_2' has been marked as deprecated: Don't use this!}} +#pragma clang restrict_expansion(UNSAFE_MACRO_2, "Don't use this!") 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,27 @@ +// expected-error@+1{{expected (}} +#pragma clang restrict_expansion + +// expected-error@+1{{expected identifier}} +#pragma clang restrict_expansion(4 + +// expected-error@+1{{no macro named foo}} +#pragma clang restrict_expansion(foo) + + +#define UNSAFE_MACRO 1 +// expected-note@+8{{macro marked 'restrict_expansion' here}} +// expected-note@+7{{macro marked 'restrict_expansion' here}} +// expected-note@+6{{macro marked 'restrict_expansion' here}} +// expected-note@+5{{macro marked 'restrict_expansion' here}} +// expected-note@+4{{macro marked 'restrict_expansion' here}} +// expected-note@+3{{macro marked 'restrict_expansion' here}} +// expected-note@+2{{macro marked 'restrict_expansion' here}} +// expected-note@+1{{macro marked 'restrict_expansion' here}} +#pragma clang restrict_expansion(UNSAFE_MACRO, "Don't use this!") + +#define UNSAFE_MACRO_2 2 +// expected-note@+1{{macro marked 'restrict_expansion' here}} +#pragma clang restrict_expansion(UNSAFE_MACRO_2) + +// expected-error@+1{{expected )}} +#pragma clang deprecated(UNSAFE_MACRO diff --git a/clang/test/Lexer/pedantic-macro-interplay.c b/clang/test/Lexer/pedantic-macro-interplay.c new file mode 100644 --- /dev/null +++ b/clang/test/Lexer/pedantic-macro-interplay.c @@ -0,0 +1,14 @@ +// RUN: %clang_cc1 -Wpedantic-macros %s -fsyntax-only -verify + +// This test verifies that the -Wpedantic-macros warnings don't fire in the +// annotation pragmas themselves. This ensures you can annotate macros with +// more than one of the pragmas without spewing warnings all over the code. + +#include "Inputs/pedantic-macro-interplay.h" + +#define UNSAFE_MACRO_2 1 +#pragma clang deprecated(UNSAFE_MACRO_2, "Don't use this!") +// not-expected-warning@+1{{macro 'UNSAFE_MACRO_2' has been marked as deprecated: Don't use this!}} +#pragma clang restrict_expansion(UNSAFE_MACRO_2, "Don't use this!") + +// expected-no-diagnostics 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 -Wrestrict-expansion %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