beanz created this revision.
beanz added reviewers: aaron.ballman, rsmith, lgerbarg, pete, lebedev.ri, 
dexonsmith.
beanz requested review of this revision.
Herald added a project: clang.

This patch adds a new preprocessor extension ``#pragma clang final``
which enables warning on undefinition and re-definition of macros.

The intent of this warning is to extend beyond ``-Wmacro-redefined`` to
warn against any and all alterations to macros that are marked `final`.

This warning is part of the ``-Wpedantic-macros`` diagnostics group.


Repository:
  rG LLVM Github Monorepo

https://reviews.llvm.org/D108567

Files:
  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/Pragma.cpp
  clang/lib/Lex/Preprocessor.cpp
  clang/test/Lexer/Inputs/final-macro.h
  clang/test/Lexer/final-macro.c
  clang/test/Lexer/pedantic-macro-interplay.c

Index: clang/test/Lexer/pedantic-macro-interplay.c
===================================================================
--- clang/test/Lexer/pedantic-macro-interplay.c
+++ 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;
Index: clang/test/Lexer/final-macro.c
===================================================================
--- /dev/null
+++ clang/test/Lexer/final-macro.c
@@ -0,0 +1,40 @@
+// RUN: %clang_cc1 -Wfinal-macro %s -fsyntax-only -verify
+
+// Test warning production
+// expected-note@+1{{previous definition is here}}
+#define Foo 1
+// expected-note@+2{{macro marked 'final' here}}
+// expected-note@+1{{macro marked 'final' here}}
+#pragma clang final(Foo)
+#pragma clang deprecated(Foo)
+#pragma clang header_unsafe(Foo)
+
+// 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
Index: clang/test/Lexer/Inputs/final-macro.h
===================================================================
--- /dev/null
+++ 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
Index: clang/lib/Lex/Preprocessor.cpp
===================================================================
--- clang/lib/Lex/Preprocessor.cpp
+++ clang/lib/Lex/Preprocessor.cpp
@@ -1413,26 +1413,46 @@
   return true;
 }
 
-void Preprocessor::emitMacroDeprecationWarning(const Token &Identifier) {
-  auto DepMsg = getMacroDeprecationMsg(Identifier.getIdentifierInfo());
-  if (DepMsg.first.empty())
+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.first;
-  Diag(DepMsg.second, diag::note_pp_macro_annotation) << 0;
+        << 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;
Index: clang/lib/Lex/Pragma.cpp
===================================================================
--- clang/lib/Lex/Pragma.cpp
+++ clang/lib/Lex/Pragma.cpp
@@ -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->getName();
+      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");
Index: clang/lib/Lex/PPDirectives.cpp
===================================================================
--- clang/lib/Lex/PPDirectives.cpp
+++ 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);
@@ -3013,6 +3019,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())
Index: clang/include/clang/Lex/Preprocessor.h
===================================================================
--- clang/include/clang/Lex/Preprocessor.h
+++ clang/include/clang/Lex/Preprocessor.h
@@ -798,12 +798,35 @@
   /// annotation pragma for use producing diagnostics and notes.
   using MsgLocationPair = std::pair<std::string, SourceLocation>;
 
-  /// Deprecation messages for macros provided in #pragma clang deprecated.
-  llvm::DenseMap<const IdentifierInfo *, MsgLocationPair> MacroDeprecationMsgs;
+  struct MacroAnnotationInfo {
+    SourceLocation Location;
+    std::string Message;
+  };
+
+  struct MacroAnnotations {
+    llvm::Optional<MacroAnnotationInfo> DeprecationInfo;
+    llvm::Optional<MacroAnnotationInfo> RestrictExpansionInfo;
+    llvm::Optional<SourceLocation> 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<const IdentifierInfo *, MsgLocationPair>
-      RestrictExpansionMacroMsgs;
+  /// Warning information for macro annotations
+  llvm::DenseMap<const IdentifierInfo *, MacroAnnotations> AnnotationInfos;
 
   /// A "freelist" of MacroArg objects that can be
   /// reused for quick allocation.
@@ -2411,36 +2434,54 @@
 
   void addMacroDeprecationMsg(const IdentifierInfo *II, std::string Msg,
                               SourceLocation AnnotationLoc) {
-    MacroDeprecationMsgs.insert(
-        std::make_pair(II, std::make_pair(std::move(Msg), AnnotationLoc)));
-  }
-
-  MsgLocationPair getMacroDeprecationMsg(const IdentifierInfo *II) {
-    return MacroDeprecationMsgs.find(II)->second;
+    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<unsigned>
   getSkippedRangeForExcludedConditionalBlock(SourceLocation HashLoc);
Index: clang/include/clang/Basic/IdentifierTable.h
===================================================================
--- clang/include/clang/Basic/IdentifierTable.h
+++ 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.
Index: clang/include/clang/Basic/DiagnosticLexKinds.td
===================================================================
--- clang/include/clang/Basic/DiagnosticLexKinds.td
+++ clang/include/clang/Basic/DiagnosticLexKinds.td
@@ -536,7 +536,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{un|re}1defined">,
+  InGroup<FinalMacro>;
 
 // - #pragma execution_character_set(...)
 def warn_pragma_exec_charset_expected :
Index: clang/include/clang/Basic/DiagnosticGroups.td
===================================================================
--- clang/include/clang/Basic/DiagnosticGroups.td
+++ 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">;
@@ -1318,4 +1319,5 @@
                     [DeprecatedPragma,
                      MacroRedefined,
                      BuiltinMacroRedefined,
-                     RestrictExpansionMacro]>;
+                     RestrictExpansionMacro,
+                     FinalMacro]>;
Index: clang/docs/LanguageExtensions.rst
===================================================================
--- clang/docs/LanguageExtensions.rst
+++ clang/docs/LanguageExtensions.rst
@@ -3965,6 +3965,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)
+
+   #undef FINAL_MACRO  // warning: FINAL_MACRO is marked final and should not be undefined
+   #define FINAL_MACRO // warning: FINAL_MACRO is marked final and should not be redefined
+
+This is useful for enforcing system-provided macros that should not be altered
+in user headers or code. This is controlled by ``-Wpedantic-macros`` which also
+includes the related ``-Wmacro-redefined``, which warns on redefinition of
+macros without undefining them first.
+
 Extended Integer Types
 ======================
 
_______________________________________________
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits

Reply via email to