erik.pilkington created this revision.
erik.pilkington added reviewers: arphaman, aaron.ballman.
Herald added subscribers: dexonsmith, jkorous.

One problem with defining macros that expand to _Pragma("clang attribute")` is 
that they don't nest very well:

  // In some header...
  #define ASSUME_X_BEGIN _Pragma ("clang attribute push(__attribute__((x)), 
apply_to=variable)")
  #define ASSUME_X_END _Pragma ("clang attribute pop")
  
  #define ASSUME_Y_BEGIN _Pragma("clang attribute push(__attribute__((y)), 
apply_to=variable)")
  #define ASSUME_Y_END _Pragma("clang attribute pop")
  
  // in some source file...
  
  ASSUME_X_BEGIN
  // lots of code
  ASSUME_Y_BEGIN
  // some more code
  ASSUME_X_END // oops! Popped Y, but meant to pop X.
  // some other code
  ASSUME_Y_END // ditto

This patch adds a way to fix this by adding labels to push/pop directives. A 
`clang attribute pop`s with a label will only pop a `push`ed group with that 
same label. Macro authors can then add a unique label to their BEGIN/END 
functions, which would cause the code above to work fine.

Thanks for taking a look!
Erik


Repository:
  rC Clang

https://reviews.llvm.org/D55628

Files:
  clang/docs/LanguageExtensions.rst
  clang/include/clang/Basic/DiagnosticSemaKinds.td
  clang/include/clang/Sema/Sema.h
  clang/lib/Parse/ParsePragma.cpp
  clang/lib/Sema/SemaAttr.cpp
  clang/test/Sema/pragma-attribute-label.c

Index: clang/test/Sema/pragma-attribute-label.c
===================================================================
--- /dev/null
+++ clang/test/Sema/pragma-attribute-label.c
@@ -0,0 +1,19 @@
+// RUN: %clang_cc1 -fsyntax-only -verify %s
+
+#pragma clang attribute push MY_LABEL (__attribute__((annotate)), apply_to=function) // expected-error 2 {{'annotate' attribute}}
+
+int some_func(); // expected-note{{when applied to this declaration}}
+
+#pragma clang attribute pop // expected-error{{'#pragma clang attribute pop' with no matching '#pragma clang attribute push'}}
+#pragma clang attribute pop NOT_MY_LABEL // expected-error{{'#pragma clang attribute pop NOT_MY_LABEL' with no matching '#pragma clang attribute push NOT_MY_LABEL'}}
+
+#pragma clang attribute push MY_OTHER_LABEL (__attribute__((annotate)), apply_to=function) // expected-error 2 {{'annotate' attribute}}
+
+int some_other_func(); // expected-note 2 {{when applied to this declaration}}
+
+// Out of order!
+#pragma clang attribute pop MY_LABEL
+
+int some_other_other_func(); // expected-note 1 {{when applied to this declaration}}
+
+#pragma clang attribute pop MY_OTHER_LABEL
Index: clang/lib/Sema/SemaAttr.cpp
===================================================================
--- clang/lib/Sema/SemaAttr.cpp
+++ clang/lib/Sema/SemaAttr.cpp
@@ -631,28 +631,45 @@
       {PragmaLoc, &Attribute, std::move(SubjectMatchRules), /*IsUsed=*/false});
 }
 
-void Sema::ActOnPragmaAttributeEmptyPush(SourceLocation PragmaLoc) {
+void Sema::ActOnPragmaAttributeEmptyPush(SourceLocation PragmaLoc,
+                                         IdentifierInfo *PushLabel) {
   PragmaAttributeStack.emplace_back();
   PragmaAttributeStack.back().Loc = PragmaLoc;
+  PragmaAttributeStack.back().Label = PushLabel;
 }
 
-void Sema::ActOnPragmaAttributePop(SourceLocation PragmaLoc) {
+void Sema::ActOnPragmaAttributePop(SourceLocation PragmaLoc,
+                                   IdentifierInfo *PushLabel) {
   if (PragmaAttributeStack.empty()) {
     Diag(PragmaLoc, diag::err_pragma_attribute_stack_mismatch);
     return;
   }
 
-  for (const PragmaAttributeEntry &Entry :
-       PragmaAttributeStack.back().Entries) {
-    if (!Entry.IsUsed) {
-      assert(Entry.Attribute && "Expected an attribute");
-      Diag(Entry.Attribute->getLoc(), diag::warn_pragma_attribute_unused)
-          << Entry.Attribute->getName();
-      Diag(PragmaLoc, diag::note_pragma_attribute_region_ends_here);
+  // Dig back through the stack trying to find the group that corrisponds to
+  // PushLabel. Note that this works fine if no label is present, think of
+  // unlabeled push/pops as having an implicit "nullptr" label.
+  for (size_t Index = PragmaAttributeStack.size(); Index;) {
+    --Index;
+    if (PragmaAttributeStack[Index].Label == PushLabel) {
+      for (const PragmaAttributeEntry &Entry :
+           PragmaAttributeStack[Index].Entries) {
+        if (!Entry.IsUsed) {
+          assert(Entry.Attribute && "Expected an attribute");
+          Diag(Entry.Attribute->getLoc(), diag::warn_pragma_attribute_unused)
+              << Entry.Attribute->getName();
+          Diag(PragmaLoc, diag::note_pragma_attribute_region_ends_here);
+        }
+      }
+      PragmaAttributeStack.erase(PragmaAttributeStack.begin() + Index);
+      return;
     }
   }
 
-  PragmaAttributeStack.pop_back();
+  if (PushLabel)
+    Diag(PragmaLoc, diag::err_pragma_attribute_stack_mismatch_label)
+        << PushLabel->getName();
+  else
+    Diag(PragmaLoc, diag::err_pragma_attribute_stack_mismatch);
 }
 
 void Sema::AddPragmaAttributes(Scope *S, Decl *D) {
Index: clang/lib/Parse/ParsePragma.cpp
===================================================================
--- clang/lib/Parse/ParsePragma.cpp
+++ clang/lib/Parse/ParsePragma.cpp
@@ -1139,6 +1139,7 @@
   enum ActionType { Push, Pop, Attribute };
   ParsedAttributes &Attributes;
   ActionType Action;
+  IdentifierInfo *PushLabel = nullptr;
   ArrayRef<Token> Tokens;
 
   PragmaAttributeInfo(ParsedAttributes &Attributes) : Attributes(Attributes) {}
@@ -1393,7 +1394,7 @@
   auto *Info = static_cast<PragmaAttributeInfo *>(Tok.getAnnotationValue());
   if (Info->Action == PragmaAttributeInfo::Pop) {
     ConsumeAnnotationToken();
-    Actions.ActOnPragmaAttributePop(PragmaLoc);
+    Actions.ActOnPragmaAttributePop(PragmaLoc, Info->PushLabel);
     return;
   }
   // Parse the actual attribute with its arguments.
@@ -1403,7 +1404,7 @@
 
   if (Info->Action == PragmaAttributeInfo::Push && Info->Tokens.empty()) {
     ConsumeAnnotationToken();
-    Actions.ActOnPragmaAttributeEmptyPush(PragmaLoc);
+    Actions.ActOnPragmaAttributeEmptyPush(PragmaLoc, Info->PushLabel);
     return;
   }
 
@@ -1555,7 +1556,7 @@
 
   // Handle a mixed push/attribute by desurging to a push, then an attribute.
   if (Info->Action == PragmaAttributeInfo::Push)
-    Actions.ActOnPragmaAttributeEmptyPush(PragmaLoc);
+    Actions.ActOnPragmaAttributeEmptyPush(PragmaLoc, Info->PushLabel);
 
   Actions.ActOnPragmaAttributeAttribute(Attribute, PragmaLoc,
                                         std::move(SubjectMatchRules));
@@ -3163,6 +3164,14 @@
     PP.Lex(Tok);
   }
 
+  // Parse the optional push/pop label.
+  if ((Info->Action == PragmaAttributeInfo::Push ||
+       Info->Action == PragmaAttributeInfo::Pop) &&
+      Tok.is(tok::identifier)) {
+    Info->PushLabel = Tok.getIdentifierInfo();
+    PP.Lex(Tok);
+  }
+
   // Parse the actual attribute.
   if ((Info->Action == PragmaAttributeInfo::Push && Tok.isNot(tok::eod)) ||
       Info->Action == PragmaAttributeInfo::Attribute) {
Index: clang/include/clang/Sema/Sema.h
===================================================================
--- clang/include/clang/Sema/Sema.h
+++ clang/include/clang/Sema/Sema.h
@@ -503,6 +503,8 @@
   struct PragmaAttributeGroup {
     /// The location of the push attribute.
     SourceLocation Loc;
+    /// The label of this push group.
+    IdentifierInfo *Label;
     SmallVector<PragmaAttributeEntry, 2> Entries;
   };
 
@@ -8498,10 +8500,12 @@
   void ActOnPragmaAttributeAttribute(ParsedAttr &Attribute,
                                      SourceLocation PragmaLoc,
                                      attr::ParsedSubjectMatchRuleSet Rules);
-  void ActOnPragmaAttributeEmptyPush(SourceLocation PragmaLoc);
+  void ActOnPragmaAttributeEmptyPush(SourceLocation PragmaLoc,
+                                     IdentifierInfo *PushLabel);
 
   /// Called on well-formed '\#pragma clang attribute pop'.
-  void ActOnPragmaAttributePop(SourceLocation PragmaLoc);
+  void ActOnPragmaAttributePop(SourceLocation PragmaLoc,
+                               IdentifierInfo *PushLabel);
 
   /// Adds the attributes that have been specified using the
   /// '\#pragma clang attribute push' directives to the given declaration.
Index: clang/include/clang/Basic/DiagnosticSemaKinds.td
===================================================================
--- clang/include/clang/Basic/DiagnosticSemaKinds.td
+++ clang/include/clang/Basic/DiagnosticSemaKinds.td
@@ -837,6 +837,9 @@
   "attribute %0 can't be applied to %1">;
 def err_pragma_attribute_stack_mismatch : Error<
   "'#pragma clang attribute pop' with no matching '#pragma clang attribute push'">;
+def err_pragma_attribute_stack_mismatch_label : Error<
+  "'#pragma clang attribute pop %0' with no matching "
+  "'#pragma clang attribute push %0'">;
 def warn_pragma_attribute_unused : Warning<
   "unused attribute %0 in '#pragma clang attribute push' region">,
   InGroup<PragmaClangAttribute>;
Index: clang/docs/LanguageExtensions.rst
===================================================================
--- clang/docs/LanguageExtensions.rst
+++ clang/docs/LanguageExtensions.rst
@@ -2663,36 +2663,42 @@
 shorthand for when you want to add one attribute to a new scope. Multiple push
 directives can be nested inside each other.
 
+Because multiple push directives can be nested, if you're writing a macro that
+expands to ``_Pragma("clang attribute")`` it's good hygiene to add a label to
+your push/pop directives. A pop directive with a label will pop the innermost
+push that has that same label. This will ensure that another macro's ``pop``
+won't inadvertently pop your attribute.
+
 The attributes that are used in the ``#pragma clang attribute`` directives
 can be written using the GNU-style syntax:
 
 .. code-block:: c++
 
-  #pragma clang attribute push (__attribute__((annotate("custom"))), apply_to = function)
+  #pragma clang attribute push ANNOTATE_CUSTOM (__attribute__((annotate("custom"))), apply_to = function)
 
   void function(); // The function now has the annotate("custom") attribute
 
-  #pragma clang attribute pop
+  #pragma clang attribute pop ANNOTATE_CUSTOM
 
 The attributes can also be written using the C++11 style syntax:
 
 .. code-block:: c++
 
-  #pragma clang attribute push ([[noreturn]], apply_to = function)
+  #pragma clang attribute push NO_RETURN ([[noreturn]], apply_to = function)
 
   void function(); // The function now has the [[noreturn]] attribute
 
-  #pragma clang attribute pop
+  #pragma clang attribute pop NO_RETURN
 
 The ``__declspec`` style syntax is also supported:
 
 .. code-block:: c++
 
-  #pragma clang attribute push (__declspec(dllexport), apply_to = function)
+  #pragma clang attribute push DLL_EXP (__declspec(dllexport), apply_to = function)
 
   void function(); // The function now has the __declspec(dllexport) attribute
 
-  #pragma clang attribute pop
+  #pragma clang attribute pop DLL_EXP
 
 A single push directive accepts only one attribute regardless of the syntax
 used.
_______________________________________________
cfe-commits mailing list
cfe-commits@lists.llvm.org
http://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits

Reply via email to