This revision was automatically updated to reflect the committed changes.
Closed by commit rG43de869d77f7: Implement #pragma clang restrict_expansion 
(authored by beanz).

Repository:
  rG LLVM Github Monorepo

CHANGES SINCE LAST ACTION
  https://reviews.llvm.org/D107095/new/

https://reviews.llvm.org/D107095

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

Index: clang/test/Lexer/unsafe-macro.c
===================================================================
--- /dev/null
+++ 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
Index: clang/test/Lexer/pedantic-macro-interplay.c
===================================================================
--- /dev/null
+++ 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
Index: clang/test/Lexer/Inputs/unsafe-macro.h
===================================================================
--- /dev/null
+++ 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
Index: clang/test/Lexer/Inputs/unsafe-macro-2.h
===================================================================
--- /dev/null
+++ 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
Index: clang/test/Lexer/Inputs/pedantic-macro-interplay.h
===================================================================
--- /dev/null
+++ 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!")
Index: clang/lib/Lex/Preprocessor.cpp
===================================================================
--- clang/lib/Lex/Preprocessor.cpp
+++ 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;
Index: clang/lib/Lex/Pragma.cpp
===================================================================
--- clang/lib/Lex/Pragma.cpp
+++ 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");
Index: clang/include/clang/Lex/Preprocessor.h
===================================================================
--- clang/include/clang/Lex/Preprocessor.h
+++ 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<SourceLocation, 32>;
   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<std::string, SourceLocation>;
+
+  /// Deprecation messages for macros provided in #pragma clang deprecated.
   llvm::DenseMap<const IdentifierInfo *, std::string> MacroDeprecationMsgs;
 
+  /// Usage warning for macros marked by #pragma clang restrict_expansion.
+  llvm::DenseMap<const IdentifierInfo *, MsgLocationPair>
+      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<unsigned>
   getSkippedRangeForExcludedConditionalBlock(SourceLocation HashLoc);
 
Index: clang/include/clang/Basic/IdentifierTable.h
===================================================================
--- clang/include/clang/Basic/IdentifierTable.h
+++ 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.
Index: clang/include/clang/Basic/DiagnosticLexKinds.td
===================================================================
--- clang/include/clang/Basic/DiagnosticLexKinds.td
+++ clang/include/clang/Basic/DiagnosticLexKinds.td
@@ -528,6 +528,16 @@
   ExtWarn<"macro %0 has been marked as deprecated%select{|: %2}1">,
   InGroup<DeprecatedPragma>;
 
+// - #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<RestrictExpansionMacro>;
+
+// - 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'">,
Index: clang/include/clang/Basic/DiagnosticGroups.td
===================================================================
--- clang/include/clang/Basic/DiagnosticGroups.td
+++ 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]>;
Index: clang/docs/LanguageExtensions.rst
===================================================================
--- clang/docs/LanguageExtensions.rst
+++ 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, "<reason>")
+
+   /// Foo.h
+   struct Foo {
+   #if TARGET_ARM // warning: TARGET_ARM is marked unsafe in headers: <reason>
+     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
 ======================
 
_______________________________________________
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits

Reply via email to