From: Chris Bieneman Date: Sat, 24 Jul 2021 00:43:45 +0000 (-0500) Subject: Support macro deprecation #pragma clang deprecated X-Git-Tag: upstream/15.0.7~35190 X-Git-Url: http://review.tizen.org/git/?a=commitdiff_plain;h=26c695b7893071d5e69afbaa70c4850ab2e468be;p=platform%2Fupstream%2Fllvm.git Support macro deprecation #pragma clang deprecated This patch adds `#pragma clang deprecated` to enable deprecation of preprocessor macros. The macro must be defined before `#pragma clang deprecated`. When deprecating a macro a custom message may be optionally provided. Warnings are emitted at the use site of a deprecated macro, and can be controlled via the `-Wdeprecated` warning group. This patch takes some rough inspiration and a few lines of code from https://reviews.llvm.org/D67935. Reviewed By: aaron.ballman Differential Revision: https://reviews.llvm.org/D106732 --- diff --git a/clang/docs/LanguageExtensions.rst b/clang/docs/LanguageExtensions.rst index 462aa89..8dafcb2 100644 --- a/clang/docs/LanguageExtensions.rst +++ b/clang/docs/LanguageExtensions.rst @@ -3887,6 +3887,24 @@ Since the size of ``buffer`` can't be known at compile time, Clang will fold as ``__builtin_dynamic_object_size(buffer, 0)``, Clang will fold it into ``size``, providing some extra runtime safety. +Deprecating Macros +================== + +Clang supports the pragma ``#pragma clang deprecated``, which can be used to +provide deprecation warnings for macro uses. For example: + +.. code-block:: c + #define MIN(x, y) x < y ? x : y + #pragma clang deprecated(MIN, "use std::min instead") + + void min(int a, int b) { + return MIN(a, b); // warning: MIN is deprecated: use std::min instead + } + +``#pragma clang deprecated`` should be preferred for this purpose over +``#pragma GCC warning`` because the warning can be controlled with +``-Wdeprecated``. + Extended Integer Types ====================== diff --git a/clang/include/clang/Basic/DiagnosticGroups.td b/clang/include/clang/Basic/DiagnosticGroups.td index 4b4928a..6857c88 100644 --- a/clang/include/clang/Basic/DiagnosticGroups.td +++ b/clang/include/clang/Basic/DiagnosticGroups.td @@ -184,6 +184,7 @@ def DeprecatedThisCapture : DiagGroup<"deprecated-this-capture">; def DeprecatedVolatile : DiagGroup<"deprecated-volatile">; def DeprecatedWritableStr : DiagGroup<"deprecated-writable-strings", [CXX11CompatDeprecatedWritableStr]>; +def DeprecatedPragma : DiagGroup<"deprecated-pragma">; // FIXME: Why is DeprecatedImplementations not in this group? def Deprecated : DiagGroup<"deprecated", [DeprecatedAnonEnumEnumConversion, DeprecatedArrayCompare, @@ -198,6 +199,7 @@ def Deprecated : DiagGroup<"deprecated", [DeprecatedAnonEnumEnumConversion, DeprecatedEnumEnumConversion, DeprecatedEnumFloatConversion, DeprecatedIncrementBool, + DeprecatedPragma, DeprecatedRegister, DeprecatedThisCapture, DeprecatedVolatile, diff --git a/clang/include/clang/Basic/DiagnosticLexKinds.td b/clang/include/clang/Basic/DiagnosticLexKinds.td index ce6d0d0..174f6c3 100644 --- a/clang/include/clang/Basic/DiagnosticLexKinds.td +++ b/clang/include/clang/Basic/DiagnosticLexKinds.td @@ -519,6 +519,11 @@ def warn_pragma_warning_expected_number : ExtWarn<"#pragma warning expected a warning number">, InGroup; +// - #pragma deprecated(...) +def warn_pragma_deprecated_macro_use : + ExtWarn<"macro %0 has been marked as deprecated%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 index f2379c7..d75d43f 100644 --- a/clang/include/clang/Basic/IdentifierTable.h +++ b/clang/include/clang/Basic/IdentifierTable.h @@ -121,7 +121,10 @@ class alignas(IdentifierInfoAlignment) IdentifierInfo { // True if this is a mangled OpenMP variant name. unsigned IsMangledOpenMPVariantName : 1; - // 28 bits left in a 64-bit word. + // True if this is a deprecated macro + unsigned IsDeprecatedMacro : 1; + + // 24 bits left in a 64-bit word. // Managed by the language front-end. void *FETokenInfo = nullptr; @@ -134,7 +137,8 @@ class alignas(IdentifierInfoAlignment) IdentifierInfo { IsPoisoned(false), IsCPPOperatorKeyword(false), NeedsHandleIdentifier(false), IsFromAST(false), ChangedAfterLoad(false), FEChangedAfterLoad(false), RevertedTokenID(false), OutOfDate(false), - IsModulesImport(false), IsMangledOpenMPVariantName(false) {} + IsModulesImport(false), IsMangledOpenMPVariantName(false), + IsDeprecatedMacro(false) {} public: IdentifierInfo(const IdentifierInfo &) = delete; @@ -183,6 +187,7 @@ public: HadMacro = true; } else { RecomputeNeedsHandleIdentifier(); + setIsDeprecatedMacro(false); } } /// Returns true if this identifier was \#defined to some value at any @@ -192,6 +197,18 @@ public: return HadMacro; } + bool isDeprecatedMacro() const { return IsDeprecatedMacro; } + + void setIsDeprecatedMacro(bool Val) { + if (IsDeprecatedMacro == Val) + return; + IsDeprecatedMacro = 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 index a089d16..45ee0ad 100644 --- a/clang/include/clang/Lex/Preprocessor.h +++ b/clang/include/clang/Lex/Preprocessor.h @@ -791,6 +791,9 @@ private: using WarnUnusedMacroLocsTy = llvm::SmallDenseSet; WarnUnusedMacroLocsTy WarnUnusedMacroLocs; + /// Deprecation messages for macros provided in #pragma clang deprecated + llvm::DenseMap MacroDeprecationMsgs; + /// A "freelist" of MacroArg objects that can be /// reused for quick allocation. MacroArgs *MacroArgCache = nullptr; @@ -2392,6 +2395,19 @@ public: /// 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 emitMacroExpansionWarnings(const Token &Identifier); + private: Optional getSkippedRangeForExcludedConditionalBlock(SourceLocation HashLoc); diff --git a/clang/lib/Lex/PPDirectives.cpp b/clang/lib/Lex/PPDirectives.cpp index 556dd8d..60075d0 100644 --- a/clang/lib/Lex/PPDirectives.cpp +++ b/clang/lib/Lex/PPDirectives.cpp @@ -617,6 +617,10 @@ void Preprocessor::SkipExcludedConditionalBlock(SourceLocation HashTokenLoc, // If this is in a skipping block or if we're already handled this #if // block, don't bother parsing the condition. if (CondInfo.WasSkipping || CondInfo.FoundNonSkip) { + // FIXME: We should probably do at least some minimal parsing of the + // condition to verify that it is well-formed. The current state + // allows #elif* directives with completely malformed (or missing) + // conditions. DiscardUntilEndOfDirective(); } else { // Restore the value of LexingRawMode so that identifiers are @@ -656,6 +660,10 @@ void Preprocessor::SkipExcludedConditionalBlock(SourceLocation HashTokenLoc, // If this is in a skipping block or if we're already handled this #if // block, don't bother parsing the condition. if (CondInfo.WasSkipping || CondInfo.FoundNonSkip) { + // FIXME: We should probably do at least some minimal parsing of the + // condition to verify that it is well-formed. The current state + // allows #elif* directives with completely malformed (or missing) + // conditions. DiscardUntilEndOfDirective(); } else { // Restore the value of LexingRawMode so that identifiers are @@ -674,6 +682,8 @@ void Preprocessor::SkipExcludedConditionalBlock(SourceLocation HashTokenLoc, continue; } + emitMacroExpansionWarnings(MacroNameTok); + CheckEndOfDirective(IsElifDef ? "elifdef" : "elifndef"); IdentifierInfo *MII = MacroNameTok.getIdentifierInfo(); @@ -3048,6 +3058,8 @@ void Preprocessor::HandleIfdefDirective(Token &Result, return; } + emitMacroExpansionWarnings(MacroNameTok); + // Check to see if this is the last token on the #if[n]def line. CheckEndOfDirective(isIfndef ? "ifndef" : "ifdef"); diff --git a/clang/lib/Lex/PPExpressions.cpp b/clang/lib/Lex/PPExpressions.cpp index cab4bab..1ebfae6 100644 --- a/clang/lib/Lex/PPExpressions.cpp +++ b/clang/lib/Lex/PPExpressions.cpp @@ -133,6 +133,8 @@ static bool EvaluateDefined(PPValue &Result, Token &PeekTok, DefinedTracker &DT, Result.Val.setIsUnsigned(false); // Result is signed intmax_t. DT.IncludedUndefinedIds = !Macro; + PP.emitMacroExpansionWarnings(PeekTok); + // If there is a macro, mark it used. if (Result.Val != 0 && ValueLive) PP.markMacroAsUsed(Macro.getMacroInfo()); diff --git a/clang/lib/Lex/PPMacroExpansion.cpp b/clang/lib/Lex/PPMacroExpansion.cpp index 589d3c3..d781c89 100644 --- a/clang/lib/Lex/PPMacroExpansion.cpp +++ b/clang/lib/Lex/PPMacroExpansion.cpp @@ -476,6 +476,8 @@ bool Preprocessor::isNextPPTokenLParen() { /// expanded as a macro, handle it and return the next token as 'Identifier'. bool Preprocessor::HandleMacroExpandedIdentifier(Token &Identifier, const MacroDefinition &M) { + emitMacroExpansionWarnings(Identifier); + MacroInfo *MI = M.getMacroInfo(); // If this is a macro expansion in the "#if !defined(x)" line for the file, diff --git a/clang/lib/Lex/Pragma.cpp b/clang/lib/Lex/Pragma.cpp index c89061b..cb3c5a1 100644 --- a/clang/lib/Lex/Pragma.cpp +++ b/clang/lib/Lex/Pragma.cpp @@ -1911,6 +1911,57 @@ struct PragmaRegionHandler : public PragmaHandler { } }; +/// "\#pragma clang deprecated(...)" +/// +/// The syntax is +/// \code +/// #pragma clang deprecate(MACRO_NAME [, Message]) +/// \endcode +struct PragmaDeprecatedHandler : public PragmaHandler { + PragmaDeprecatedHandler() : PragmaHandler("deprecated") {} + + 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; + } + + 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->getName(); + return; + } + + PP.Lex(Tok); + if (Tok.is(tok::comma)) { + PP.Lex(Tok); + if (!PP.FinishLexStringLiteral(Tok, MessageString, + "#pragma clang deprecated", + /*AllowMacroExpansion=*/true)) + return; + } + + if (Tok.isNot(tok::r_paren)) { + PP.Diag(Tok, diag::err_expected) << ")"; + return; + } + + II->setIsDeprecatedMacro(true); + if (!MessageString.empty()) + PP.addMacroDeprecationMsg(II, std::move(MessageString)); + } +}; + } // namespace /// RegisterBuiltinPragmas - Install the standard preprocessor pragmas: @@ -1939,6 +1990,7 @@ void Preprocessor::RegisterBuiltinPragmas() { AddPragmaHandler("clang", new PragmaDiagnosticHandler("clang")); AddPragmaHandler("clang", new PragmaARCCFCodeAuditedHandler()); AddPragmaHandler("clang", new PragmaAssumeNonNullHandler()); + AddPragmaHandler("clang", new PragmaDeprecatedHandler()); // #pragma clang module ... auto *ModuleHandler = new PragmaNamespace("module"); diff --git a/clang/lib/Lex/Preprocessor.cpp b/clang/lib/Lex/Preprocessor.cpp index 32ea879..a0722a2 100644 --- a/clang/lib/Lex/Preprocessor.cpp +++ b/clang/lib/Lex/Preprocessor.cpp @@ -1413,6 +1413,18 @@ bool Preprocessor::HandleComment(Token &result, SourceRange Comment) { 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; + } +} + ModuleLoader::~ModuleLoader() = default; CommentHandler::~CommentHandler() = default; diff --git a/clang/test/Lexer/deprecate-macro.c b/clang/test/Lexer/deprecate-macro.c new file mode 100644 index 0000000..c45d5fc --- /dev/null +++ b/clang/test/Lexer/deprecate-macro.c @@ -0,0 +1,98 @@ +// RUN: %clang_cc1 -Wdeprecated %s -fsyntax-only -verify + +// expected-error@+1{{expected (}} +#pragma clang deprecated + +// expected-error@+1{{expected identifier}} +#pragma clang deprecated(4 + +// expected-error@+1{{no macro named foo}} +#pragma clang deprecated(foo) + +#define bar 1 +#pragma clang deprecated(bar, "bar is deprecated use 1") + +// expected-warning@+1{{macro 'bar' has been marked as deprecated: bar is deprecated use 1}} +#if bar +#endif + +#define foo 1 +#pragma clang deprecated(foo) + +// expected-error@+1{{expected )}} +#pragma clang deprecated(foo + +// expected-warning@+1{{macro 'foo' has been marked as deprecated}} +#if foo +#endif + +// expected-warning@+1{{macro 'foo' has been marked as deprecated}} +#if defined(foo) +#endif + +// expected-warning@+1{{macro 'foo' has been marked as deprecated}} +#ifdef foo +#endif + +// expected-warning@+1{{macro 'foo' has been marked as deprecated}} +#ifndef foo +#endif + +int main(int argc, char** argv) { + // expected-error@+1{{no macro named main}} +#pragma clang deprecated(main) + + // expected-warning@+1{{macro 'foo' has been marked as deprecated}} + return foo; +} + +#define frobble 1 +#pragma clang deprecated(frobble) + +// not-expected-warning@+1{{macro 'frobble' has been marked as deprecated}} +#undef frobble // Expect no diagnostics here + +// not-expected-warning@+1{{macro 'frobble' has been marked as deprecated}} +#define frobble 1 // How about here given that this was undefined? + +// not-expected-warning@+1{{macro 'frobble' has been marked as deprecated}} +#if defined(frobble) +#endif + +// Test that we diagnose on #elif. +#if 0 +#elif foo +// expected-warning@-1{{macro 'foo' has been marked as deprecated}} +#endif + + +// Test that we diagnose on #elifdef. +#ifdef baz +#elifdef foo +// expected-warning@-1{{macro 'foo' has been marked as deprecated}} +#endif + +// Test that we diagnose on #elifndef. +#ifdef baz +#elifndef foo +#endif +// expected-warning@-2{{macro 'foo' has been marked as deprecated}} + +// FIXME: These cases are currently not handled because clang doesn't expand +// conditions on skipped #elif* blocks. See the FIXME notes in +// Preprocessor::SkipExcludedConditionalBlock. + +#ifdef frobble +// not-expected-warning@+1{{macro 'foo' has been marked as deprecated}} +#elifndef foo +#endif + +#ifdef frobble +// not-expected-warning@+1{{macro 'foo' has been marked as deprecated}} +#elifdef foo +#endif + +#if 1 +// not-expected-warning@+1{{macro 'foo' has been marked as deprecated}} +#elif foo +#endif