[llvm-branch-commits] [clang] 393e6e4 - Support macro deprecation #pragma clang deprecated

Chris Bieneman via llvm-branch-commits llvm-branch-commits at lists.llvm.org
Thu Jul 29 09:24:41 PDT 2021


Author: Chris Bieneman
Date: 2021-07-29T09:11:14-05:00
New Revision: 393e6e4cbc05f589c4dd75dbfae79ee44eb1a5f5

URL: https://github.com/llvm/llvm-project/commit/393e6e4cbc05f589c4dd75dbfae79ee44eb1a5f5
DIFF: https://github.com/llvm/llvm-project/commit/393e6e4cbc05f589c4dd75dbfae79ee44eb1a5f5.diff

LOG: 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.

Differential Revision: https://reviews.llvm.org/D106732

Added: 
    clang/test/Lexer/deprecate-macro.c

Modified: 
    clang/docs/LanguageExtensions.rst
    clang/include/clang/Basic/DiagnosticGroups.td
    clang/include/clang/Basic/DiagnosticLexKinds.td
    clang/include/clang/Basic/IdentifierTable.h
    clang/include/clang/Lex/Preprocessor.h
    clang/lib/Lex/PPDirectives.cpp
    clang/lib/Lex/PPExpressions.cpp
    clang/lib/Lex/PPMacroExpansion.cpp
    clang/lib/Lex/Pragma.cpp
    clang/lib/Lex/Preprocessor.cpp

Removed: 
    


################################################################################
diff  --git a/clang/docs/LanguageExtensions.rst b/clang/docs/LanguageExtensions.rst
index 462aa89ea789a..8dafcb2f9a724 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 4b4928a7a00e6..6857c889b72b6 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 ce6d0d0394b48..174f6c3dfd4c6 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<UnknownPragmas>;
 
+// - #pragma deprecated(...)
+def warn_pragma_deprecated_macro_use :
+  ExtWarn<"macro %0 has been marked as deprecated%select{|: %2}1">,
+  InGroup<DeprecatedPragma>;
+
 // - #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 f2379c7ddfbd1..d75d43f0398d7 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 @@ class alignas(IdentifierInfoAlignment) IdentifierInfo {
       HadMacro = true;
     } else {
       RecomputeNeedsHandleIdentifier();
+      setIsDeprecatedMacro(false);
     }
   }
   /// Returns true if this identifier was \#defined to some value at any
@@ -192,6 +197,18 @@ class alignas(IdentifierInfoAlignment) IdentifierInfo {
     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 a089d166caaa9..45ee0ad9fd21e 100644
--- a/clang/include/clang/Lex/Preprocessor.h
+++ b/clang/include/clang/Lex/Preprocessor.h
@@ -791,6 +791,9 @@ class Preprocessor {
   using WarnUnusedMacroLocsTy = llvm::SmallDenseSet<SourceLocation, 32>;
   WarnUnusedMacroLocsTy WarnUnusedMacroLocs;
 
+  /// Deprecation messages for macros provided in #pragma clang deprecated
+  llvm::DenseMap<const IdentifierInfo *, std::string> MacroDeprecationMsgs;
+
   /// A "freelist" of MacroArg objects that can be
   /// reused for quick allocation.
   MacroArgs *MacroArgCache = nullptr;
@@ -2392,6 +2395,19 @@ class Preprocessor {
   /// warnings.
   void markMacroAsUsed(MacroInfo *MI);
 
+  void addMacroDeprecationMsg(const IdentifierInfo *II, std::string Msg) {
+    MacroDeprecationMsgs.insert(std::make_pair(II, Msg));
+  }
+
+  llvm::Optional<std::string> 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<unsigned>
   getSkippedRangeForExcludedConditionalBlock(SourceLocation HashLoc);

diff  --git a/clang/lib/Lex/PPDirectives.cpp b/clang/lib/Lex/PPDirectives.cpp
index 556dd8daf652e..60075d09280d9 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 cab4bab630dc9..1ebfae606a588 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 589d3c3358850..d781c89562a06 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 c89061ba6d02e..cb3c5a1f89e31 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 32ea8791d29a8..a0722a26cb780 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 0000000000000..c45d5fc22d822
--- /dev/null
+++ b/clang/test/Lexer/deprecate-macro.c
@@ -0,0 +1,98 @@
+// RUN: %clang_cc1 -Wdeprecated %s -fsyntax-only -verify
+
+// expected-error at +1{{expected (}}
+#pragma clang deprecated
+
+// expected-error at +1{{expected identifier}}
+#pragma clang deprecated(4
+
+// expected-error at +1{{no macro named foo}}
+#pragma clang deprecated(foo)
+
+#define bar 1
+#pragma clang deprecated(bar, "bar is deprecated use 1")
+
+// expected-warning at +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 at +1{{expected )}}
+#pragma clang deprecated(foo
+
+// expected-warning at +1{{macro 'foo' has been marked as deprecated}}
+#if foo
+#endif
+
+// expected-warning at +1{{macro 'foo' has been marked as deprecated}}
+#if defined(foo)
+#endif
+
+// expected-warning at +1{{macro 'foo' has been marked as deprecated}}
+#ifdef foo
+#endif
+
+// expected-warning at +1{{macro 'foo' has been marked as deprecated}}
+#ifndef foo
+#endif
+
+int main(int argc, char** argv) {
+  // expected-error at +1{{no macro named main}}
+#pragma clang deprecated(main)
+
+  // expected-warning at +1{{macro 'foo' has been marked as deprecated}}
+  return foo;
+}
+
+#define frobble 1
+#pragma clang deprecated(frobble)
+
+// not-expected-warning at +1{{macro 'frobble' has been marked as deprecated}}
+#undef frobble // Expect no diagnostics here
+
+// not-expected-warning at +1{{macro 'frobble' has been marked as deprecated}}
+#define frobble 1 // How about here given that this was undefined?
+
+// not-expected-warning at +1{{macro 'frobble' has been marked as deprecated}}
+#if defined(frobble)
+#endif
+
+// Test that we diagnose on #elif.
+#if 0
+#elif foo
+// expected-warning at -1{{macro 'foo' has been marked as deprecated}}
+#endif
+
+
+// Test that we diagnose on #elifdef.
+#ifdef baz
+#elifdef foo
+// expected-warning at -1{{macro 'foo' has been marked as deprecated}}
+#endif
+
+// Test that we diagnose on #elifndef.
+#ifdef baz
+#elifndef foo
+#endif
+// expected-warning at -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 at +1{{macro 'foo' has been marked as deprecated}}
+#elifndef foo
+#endif
+
+#ifdef frobble
+// not-expected-warning at +1{{macro 'foo' has been marked as deprecated}}
+#elifdef foo
+#endif
+
+#if 1
+// not-expected-warning at +1{{macro 'foo' has been marked as deprecated}}
+#elif foo
+#endif


        


More information about the llvm-branch-commits mailing list