diff --git a/clang/docs/LanguageExtensions.rst b/clang/docs/LanguageExtensions.rst --- a/clang/docs/LanguageExtensions.rst +++ b/clang/docs/LanguageExtensions.rst @@ -3944,6 +3944,24 @@ This warning is controlled by ``-Wpedantic-macros``. +Final Macros +============ + +Clang supports the pragma ``#pragma clang final``, which can be used to +mark macros as final, meaning they cannot be undef'd or re-defined. For example: + +.. code-block:: c + #define FINAL_MACRO 1 + #pragma clang final(FINAL_MACRO) + + #define FINAL_MACRO // warning: FINAL_MACRO is marked final and should not be redefined + #undef FINAL_MACRO // warning: FINAL_MACRO is marked final and should not be undefined + +This is useful for enforcing system-provided macros that should not be altered +in user headers or code. This is controlled by ``-Wpedantic-macros``. Final +macros will always warn on redefinition, including situations with identical +bodies and in system headers. + 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 @@ -648,6 +648,7 @@ def ReservedIdAsMacro : DiagGroup<"reserved-macro-identifier">; def ReservedIdAsMacroAlias : DiagGroup<"reserved-id-macro", [ReservedIdAsMacro]>; def RestrictExpansionMacro : DiagGroup<"restrict-expansion">; +def FinalMacro : DiagGroup<"final-macro">; // Just silence warnings about -Wstrict-aliasing for now. def : DiagGroup<"strict-aliasing=0">; @@ -1322,4 +1323,5 @@ [DeprecatedPragma, MacroRedefined, BuiltinMacroRedefined, - RestrictExpansionMacro]>; + RestrictExpansionMacro, + FinalMacro]>; 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 @@ -551,7 +551,13 @@ // - Note for macro annotations. def note_pp_macro_annotation : - Note<"macro marked '%select{deprecated|restrict_expansion}0' here">; + Note<"macro marked '%select{deprecated|restrict_expansion|final}0' here">; + +// - #pragma clang final(...) +def warn_pragma_final_macro : + ExtWarn<"macro %0 has been marked as final and should not be " + "%select{undefined|redefined}1">, + InGroup; // - #pragma execution_character_set(...) def warn_pragma_exec_charset_expected : 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 @@ -127,7 +127,10 @@ // True if this macro is unsafe in headers. unsigned IsRestrictExpansion : 1; - // 23 bits left in a 64-bit word. + // True if this macro is final. + unsigned IsFinal : 1; + + // 22 bits left in a 64-bit word. // Managed by the language front-end. void *FETokenInfo = nullptr; @@ -141,7 +144,7 @@ NeedsHandleIdentifier(false), IsFromAST(false), ChangedAfterLoad(false), FEChangedAfterLoad(false), RevertedTokenID(false), OutOfDate(false), IsModulesImport(false), IsMangledOpenMPVariantName(false), - IsDeprecatedMacro(false), IsRestrictExpansion(false) {} + IsDeprecatedMacro(false), IsRestrictExpansion(false), IsFinal(false) {} public: IdentifierInfo(const IdentifierInfo &) = delete; @@ -189,10 +192,14 @@ 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; + // If this is a final macro, make the deprecation and header unsafe bits + // stick around after the undefinition so they apply to any redefinitions. + if (!IsFinal) { + // Because calling the setters of these calls recomputes, just set them + // manually to avoid recomputing a bunch of times. + IsDeprecatedMacro = false; + IsRestrictExpansion = false; + } RecomputeNeedsHandleIdentifier(); } } @@ -227,6 +234,10 @@ RecomputeNeedsHandleIdentifier(); } + bool isFinal() const { return IsFinal; } + + void setIsFinal(bool Val) { IsFinal = Val; } + /// 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 @@ -793,12 +793,35 @@ /// annotation pragma for use producing diagnostics and notes. using MsgLocationPair = std::pair; - /// Deprecation messages for macros provided in #pragma clang deprecated. - llvm::DenseMap MacroDeprecationMsgs; + struct MacroAnnotationInfo { + SourceLocation Location; + std::string Message; + }; + + struct MacroAnnotations { + llvm::Optional DeprecationInfo; + llvm::Optional RestrictExpansionInfo; + llvm::Optional FinalAnnotationLoc; + + static MacroAnnotations makeDeprecation(SourceLocation Loc, + std::string Msg) { + return MacroAnnotations{MacroAnnotationInfo{Loc, std::move(Msg)}, + llvm::None, llvm::None}; + } + + static MacroAnnotations makeRestrictExpansion(SourceLocation Loc, + std::string Msg) { + return MacroAnnotations{ + llvm::None, MacroAnnotationInfo{Loc, std::move(Msg)}, llvm::None}; + } + + static MacroAnnotations makeFinal(SourceLocation Loc) { + return MacroAnnotations{llvm::None, llvm::None, Loc}; + } + }; - /// Usage warning for macros marked by #pragma clang restrict_expansion. - llvm::DenseMap - RestrictExpansionMacroMsgs; + /// Warning information for macro annotations. + llvm::DenseMap AnnotationInfos; /// A "freelist" of MacroArg objects that can be /// reused for quick allocation. @@ -2402,39 +2425,56 @@ /// warnings. void markMacroAsUsed(MacroInfo *MI); - void addMacroDeprecationMsg(const IdentifierInfo *II, std::string Msg) { - MacroDeprecationMsgs.insert(std::make_pair(II, Msg)); - } - - llvm::Optional getMacroDeprecationMsg(const IdentifierInfo *II) { - auto MsgEntry = MacroDeprecationMsgs.find(II); - if (MsgEntry == MacroDeprecationMsgs.end()) - return llvm::None; - return MsgEntry->second; + void addMacroDeprecationMsg(const IdentifierInfo *II, std::string Msg, + SourceLocation AnnotationLoc) { + auto Annotations = AnnotationInfos.find(II); + if (Annotations == AnnotationInfos.end()) + AnnotationInfos.insert(std::make_pair( + II, + MacroAnnotations::makeDeprecation(AnnotationLoc, std::move(Msg)))); + else + Annotations->second.DeprecationInfo = + MacroAnnotationInfo{AnnotationLoc, std::move(Msg)}; } void addRestrictExpansionMsg(const IdentifierInfo *II, std::string Msg, SourceLocation AnnotationLoc) { - RestrictExpansionMacroMsgs.insert( - std::make_pair(II, std::make_pair(std::move(Msg), AnnotationLoc))); + auto Annotations = AnnotationInfos.find(II); + if (Annotations == AnnotationInfos.end()) + AnnotationInfos.insert( + std::make_pair(II, MacroAnnotations::makeRestrictExpansion( + AnnotationLoc, std::move(Msg)))); + else + Annotations->second.RestrictExpansionInfo = + MacroAnnotationInfo{AnnotationLoc, std::move(Msg)}; + } + + void addFinalLoc(const IdentifierInfo *II, SourceLocation AnnotationLoc) { + auto Annotations = AnnotationInfos.find(II); + if (Annotations == AnnotationInfos.end()) + AnnotationInfos.insert( + std::make_pair(II, MacroAnnotations::makeFinal(AnnotationLoc))); + else + Annotations->second.FinalAnnotationLoc = AnnotationLoc; } - MsgLocationPair getRestrictExpansionMsg(const IdentifierInfo *II) { - return RestrictExpansionMacroMsgs.find(II)->second; + const MacroAnnotations &getMacroAnnotations(const IdentifierInfo *II) const { + return AnnotationInfos.find(II)->second; } - void emitMacroExpansionWarnings(const Token &Identifier) { + void emitMacroExpansionWarnings(const Token &Identifier) const { if (Identifier.getIdentifierInfo()->isDeprecatedMacro()) emitMacroDeprecationWarning(Identifier); if (Identifier.getIdentifierInfo()->isRestrictExpansion() && !SourceMgr.isInMainFile(Identifier.getLocation())) - emitMacroUnsafeHeaderWarning(Identifier); + emitRestrictExpansionWarning(Identifier); } private: - void emitMacroDeprecationWarning(const Token &Identifier); - void emitMacroUnsafeHeaderWarning(const Token &Identifier); + void emitMacroDeprecationWarning(const Token &Identifier) const; + void emitRestrictExpansionWarning(const Token &Identifier) const; + void emitFinalMacroWarning(const Token &Identifier, bool IsUndef) const; Optional getSkippedRangeForExcludedConditionalBlock(SourceLocation HashLoc); 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 @@ -2865,6 +2865,12 @@ if (MacroNameTok.is(tok::eod)) return; + IdentifierInfo *II = MacroNameTok.getIdentifierInfo(); + // Issue a final pragma warning if we're defining a macro that was has been + // undefined and is being redefined. + if (!II->hasMacroDefinition() && II->hadMacroDefinition() && II->isFinal()) + emitFinalMacroWarning(MacroNameTok, /*IsUndef=*/false); + // If we are supposed to keep comments in #defines, reenable comment saving // mode. if (CurLexer) CurLexer->SetCommentRetentionState(KeepMacroComments); @@ -2907,6 +2913,12 @@ // Finally, if this identifier already had a macro defined for it, verify that // the macro bodies are identical, and issue diagnostics if they are not. if (const MacroInfo *OtherMI=getMacroInfo(MacroNameTok.getIdentifierInfo())) { + // Final macros are hard-mode: they always warn. Even if the bodies are + // identical. Even if they are in system headers. Even if they are things we + // would silently allow in the past. + if (MacroNameTok.getIdentifierInfo()->isFinal()) + emitFinalMacroWarning(MacroNameTok, /*IsUndef=*/false); + // In Objective-C, ignore attempts to directly redefine the builtin // definitions of the ownership qualifiers. It's still possible to // #undef them. @@ -2936,6 +2948,7 @@ // then don't bother calling MacroInfo::isIdenticalTo. if (!getDiagnostics().getSuppressSystemWarnings() || !SourceMgr.isInSystemHeader(DefineTok.getLocation())) { + if (!OtherMI->isUsed() && OtherMI->isWarnIfUnused()) Diag(OtherMI->getDefinitionLoc(), diag::pp_macro_not_used); @@ -3013,6 +3026,9 @@ auto MD = getMacroDefinition(II); UndefMacroDirective *Undef = nullptr; + if (II->isFinal()) + emitFinalMacroWarning(MacroNameTok, /*IsUndef=*/true); + // If the macro is not defined, this is a noop undef. if (const MacroInfo *MI = MD.getMacroInfo()) { if (!MI->isUsed() && MI->isWarnIfUnused()) 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 @@ -1990,7 +1990,7 @@ IdentifierInfo *II = Tok.getIdentifierInfo(); if (!II->hasMacroDefinition()) { - PP.Diag(Tok, diag::err_pp_visibility_non_macro) << II->getName(); + PP.Diag(Tok, diag::err_pp_visibility_non_macro) << II; return nullptr; } @@ -2025,8 +2025,8 @@ if (IdentifierInfo *II = HandleMacroAnnotationPragma( PP, Tok, "#pragma clang deprecated", MessageString)) { II->setIsDeprecatedMacro(true); - if (!MessageString.empty()) - PP.addMacroDeprecationMsg(II, std::move(MessageString)); + PP.addMacroDeprecationMsg(II, std::move(MessageString), + Tok.getLocation()); } } }; @@ -2053,6 +2053,47 @@ } }; +/// "\#pragma clang final(...)" +/// +/// The syntax is +/// \code +/// #pragma clang final(MACRO_NAME) +/// \endcode +struct PragmaFinalHandler : public PragmaHandler { + PragmaFinalHandler() : PragmaHandler("final") {} + + void HandlePragma(Preprocessor &PP, PragmaIntroducer Introducer, + Token &Tok) override { + std::string Macro; + + PP.Lex(Tok); + if (Tok.isNot(tok::l_paren)) { + PP.Diag(Tok, diag::err_expected) << "("; + return; + } + + PP.LexUnexpandedToken(Tok); + if (!Tok.is(tok::identifier)) { + PP.Diag(Tok, diag::err_expected) << tok::identifier; + return; + } + IdentifierInfo *II = Tok.getIdentifierInfo(); + + if (!II->hasMacroDefinition()) { + PP.Diag(Tok, diag::err_pp_visibility_non_macro) << II; + return; + } + + PP.Lex(Tok); + if (Tok.isNot(tok::r_paren)) { + PP.Diag(Tok, diag::err_expected) << ")"; + return; + } + II->setIsFinal(true); + PP.addFinalLoc(II, Tok.getLocation()); + } +}; + } // namespace /// RegisterBuiltinPragmas - Install the standard preprocessor pragmas: @@ -2084,6 +2125,7 @@ AddPragmaHandler("clang", new PragmaAssumeNonNullHandler()); AddPragmaHandler("clang", new PragmaDeprecatedHandler()); AddPragmaHandler("clang", new PragmaRestrictExpansionHandler()); + AddPragmaHandler("clang", new PragmaFinalHandler()); // #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 @@ -1409,25 +1409,46 @@ return true; } -void Preprocessor::emitMacroDeprecationWarning(const Token &Identifier) { - auto DepMsg = getMacroDeprecationMsg(Identifier.getIdentifierInfo()); - if (!DepMsg) +void Preprocessor::emitMacroDeprecationWarning(const Token &Identifier) const { + const MacroAnnotations &A = + getMacroAnnotations(Identifier.getIdentifierInfo()); + assert(A.DeprecationInfo && + "Macro deprecation warning without recorded annotation!"); + const MacroAnnotationInfo &Info = *A.DeprecationInfo; + if (Info.Message.empty()) Diag(Identifier, diag::warn_pragma_deprecated_macro_use) << Identifier.getIdentifierInfo() << 0; else Diag(Identifier, diag::warn_pragma_deprecated_macro_use) - << Identifier.getIdentifierInfo() << 1 << *DepMsg; + << Identifier.getIdentifierInfo() << 1 << Info.Message; + Diag(Info.Location, diag::note_pp_macro_annotation) << 0; } -void Preprocessor::emitMacroUnsafeHeaderWarning(const Token &Identifier) { - auto DepMsg = getRestrictExpansionMsg(Identifier.getIdentifierInfo()); - if (DepMsg.first.empty()) +void Preprocessor::emitRestrictExpansionWarning(const Token &Identifier) const { + const MacroAnnotations &A = + getMacroAnnotations(Identifier.getIdentifierInfo()); + assert(A.RestrictExpansionInfo && + "Macro restricted expansion warning without recorded annotation!"); + const MacroAnnotationInfo &Info = *A.RestrictExpansionInfo; + if (Info.Message.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; + << Identifier.getIdentifierInfo() << 1 << Info.Message; + Diag(Info.Location, diag::note_pp_macro_annotation) << 1; +} + +void Preprocessor::emitFinalMacroWarning(const Token &Identifier, + bool IsUndef) const { + const MacroAnnotations &A = + getMacroAnnotations(Identifier.getIdentifierInfo()); + assert(A.FinalAnnotationLoc && + "Final macro warning without recorded annotation!"); + + Diag(Identifier, diag::warn_pragma_final_macro) + << Identifier.getIdentifierInfo() << (IsUndef ? 0 : 1); + Diag(*A.FinalAnnotationLoc, diag::note_pp_macro_annotation) << 2; } ModuleLoader::~ModuleLoader() = default; diff --git a/clang/test/Lexer/Inputs/final-macro.h b/clang/test/Lexer/Inputs/final-macro.h new file mode 100644 --- /dev/null +++ b/clang/test/Lexer/Inputs/final-macro.h @@ -0,0 +1,4 @@ +// expected-warning@+2{{macro 'Foo' has been marked as deprecated}} +// expected-warning@+1{{macro 'Foo' has been marked as unsafe for use in headers}} +#if Foo +#endif diff --git a/clang/test/Lexer/Inputs/unsafe-macro.h b/clang/test/Lexer/Inputs/unsafe-macro.h --- a/clang/test/Lexer/Inputs/unsafe-macro.h +++ b/clang/test/Lexer/Inputs/unsafe-macro.h @@ -4,7 +4,7 @@ // expected-error@+1{{expected identifier}} #pragma clang restrict_expansion(4 -// expected-error@+1{{no macro named foo}} +// expected-error@+1{{no macro named 'foo'}} #pragma clang restrict_expansion(foo) diff --git a/clang/test/Lexer/deprecate-macro.c b/clang/test/Lexer/deprecate-macro.c --- a/clang/test/Lexer/deprecate-macro.c +++ b/clang/test/Lexer/deprecate-macro.c @@ -6,10 +6,11 @@ // expected-error@+1{{expected identifier}} #pragma clang deprecated(4 -// expected-error@+1{{no macro named foo}} +// expected-error@+1{{no macro named 'foo'}} #pragma clang deprecated(foo) #define bar 1 +// expected-note@+1{{macro marked 'deprecated' here}} #pragma clang deprecated(bar, "bar is deprecated use 1") // expected-warning@+1{{macro 'bar' has been marked as deprecated: bar is deprecated use 1}} @@ -17,6 +18,14 @@ #endif #define foo 1 +// expected-note@+8{{macro marked 'deprecated' here}} +// expected-note@+7{{macro marked 'deprecated' here}} +// expected-note@+6{{macro marked 'deprecated' here}} +// expected-note@+5{{macro marked 'deprecated' here}} +// expected-note@+4{{macro marked 'deprecated' here}} +// expected-note@+3{{macro marked 'deprecated' here}} +// expected-note@+2{{macro marked 'deprecated' here}} +// expected-note@+1{{macro marked 'deprecated' here}} #pragma clang deprecated(foo) // expected-error@+1{{expected )}} @@ -39,7 +48,7 @@ #endif int main(int argc, char** argv) { - // expected-error@+1{{no macro named main}} +// expected-error@+1{{no macro named 'main'}} #pragma clang deprecated(main) // expected-warning@+1{{macro 'foo' has been marked as deprecated}} diff --git a/clang/test/Lexer/final-macro.c b/clang/test/Lexer/final-macro.c new file mode 100644 --- /dev/null +++ b/clang/test/Lexer/final-macro.c @@ -0,0 +1,45 @@ +// RUN: %clang_cc1 -Wfinal-macro %s -fsyntax-only -verify + +// Test warning production +#define Foo 1 +// expected-note@+1 4{{macro marked 'final' here}} +#pragma clang final(Foo) + +// expected-warning@+2{{macro 'Foo' has been marked as final and should not be redefined}} +// expected-note@+1{{previous definition is here}} +#define Foo 1 + +// expected-warning@+2{{macro 'Foo' has been marked as final and should not be redefined}} +// expected-warning@+1{{'Foo' macro redefined}} +#define Foo 2 + +// expected-warning@+1{{redefining builtin macro}} +#define __TIME__ 1 + +// expected-warning@+1{{undefining builtin macro}} +#undef __TIMESTAMP__ + +// expected-warning@+1{{macro 'Foo' has been marked as final and should not be undefined}} +#undef Foo +// expected-warning@+1{{macro 'Foo' has been marked as final and should not be redefined}} +#define Foo 3 + +// Test parse errors +// expected-error@+1{{expected (}} +#pragma clang final + +// expected-error@+1{{expected )}} +#pragma clang final(Foo + +// expected-error@+1{{no macro named 'Baz'}} +#pragma clang final(Baz) + +// expected-error@+1{{expected identifier}} +#pragma clang final(4) + +// expected-error@+1{{expected (}} +#pragma clang final Baz + +// no diagnostics triggered by these pragmas. +#pragma clang deprecated(Foo) +#pragma clang restrict_expansion(Foo) diff --git a/clang/test/Lexer/pedantic-macro-interplay.c b/clang/test/Lexer/pedantic-macro-interplay.c --- a/clang/test/Lexer/pedantic-macro-interplay.c +++ b/clang/test/Lexer/pedantic-macro-interplay.c @@ -11,4 +11,17 @@ // 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 + +#define Foo 1 +#pragma clang final(Foo) +// expected-note@+2{{macro marked 'deprecated' here}} +// expected-note@+1{{macro marked 'deprecated' here}} +#pragma clang deprecated(Foo) +// expected-note@+1{{macro marked 'restrict_expansion' here}} +#pragma clang restrict_expansion(Foo) + +// Test that unsafe_header and deprecated markings stick around after the undef +#include "Inputs/final-macro.h" + +// expected-warning@+1{{macro 'Foo' has been marked as deprecated}} +const int X = Foo;