Add #pragma clang attribute

This is a recommit of r300539 that was reverted in r300543 due to test failures.
The original commit message is displayed below:

The new '#pragma clang attribute' directive can be used to apply attributes to
multiple declarations. An attribute must satisfy the following conditions to
be supported by the pragma:
- It must have a subject list that's defined in the TableGen file.
- It must be documented.
- It must not be late parsed.
- It must have a GNU/C++11 spelling.

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

llvm-svn: 300556
diff --git a/clang/lib/Sema/SemaAttr.cpp b/clang/lib/Sema/SemaAttr.cpp
index c6e3cc8..dfc5d6c 100644
--- a/clang/lib/Sema/SemaAttr.cpp
+++ b/clang/lib/Sema/SemaAttr.cpp
@@ -368,6 +368,217 @@
   D->addAttr(CFAuditedTransferAttr::CreateImplicit(Context, Loc));
 }
 
+namespace {
+
+Optional<attr::SubjectMatchRule>
+getParentAttrMatcherRule(attr::SubjectMatchRule Rule) {
+  using namespace attr;
+  switch (Rule) {
+  default:
+    return None;
+#define ATTR_MATCH_RULE(Value, Spelling, IsAbstract)
+#define ATTR_MATCH_SUB_RULE(Value, Spelling, IsAbstract, Parent, IsNegated)    \
+  case Value:                                                                  \
+    return Parent;
+#include "clang/Basic/AttrSubMatchRulesList.inc"
+  }
+}
+
+bool isNegatedAttrMatcherSubRule(attr::SubjectMatchRule Rule) {
+  using namespace attr;
+  switch (Rule) {
+  default:
+    return false;
+#define ATTR_MATCH_RULE(Value, Spelling, IsAbstract)
+#define ATTR_MATCH_SUB_RULE(Value, Spelling, IsAbstract, Parent, IsNegated)    \
+  case Value:                                                                  \
+    return IsNegated;
+#include "clang/Basic/AttrSubMatchRulesList.inc"
+  }
+}
+
+CharSourceRange replacementRangeForListElement(const Sema &S,
+                                               SourceRange Range) {
+  // Make sure that the ',' is removed as well.
+  SourceLocation AfterCommaLoc = Lexer::findLocationAfterToken(
+      Range.getEnd(), tok::comma, S.getSourceManager(), S.getLangOpts(),
+      /*SkipTrailingWhitespaceAndNewLine=*/false);
+  if (AfterCommaLoc.isValid())
+    return CharSourceRange::getCharRange(Range.getBegin(), AfterCommaLoc);
+  else
+    return CharSourceRange::getTokenRange(Range);
+}
+
+std::string
+attrMatcherRuleListToString(ArrayRef<attr::SubjectMatchRule> Rules) {
+  std::string Result;
+  llvm::raw_string_ostream OS(Result);
+  for (const auto &I : llvm::enumerate(Rules)) {
+    if (I.index())
+      OS << (I.index() == Rules.size() - 1 ? ", and " : ", ");
+    OS << "'" << attr::getSubjectMatchRuleSpelling(I.value()) << "'";
+  }
+  return OS.str();
+}
+
+} // end anonymous namespace
+
+void Sema::ActOnPragmaAttributePush(AttributeList &Attribute,
+                                    SourceLocation PragmaLoc,
+                                    attr::ParsedSubjectMatchRuleSet Rules) {
+  SmallVector<attr::SubjectMatchRule, 4> SubjectMatchRules;
+  // Gather the subject match rules that are supported by the attribute.
+  SmallVector<std::pair<attr::SubjectMatchRule, bool>, 4>
+      StrictSubjectMatchRuleSet;
+  Attribute.getMatchRules(LangOpts, StrictSubjectMatchRuleSet);
+
+  // Figure out which subject matching rules are valid.
+  if (StrictSubjectMatchRuleSet.empty()) {
+    // Check for contradicting match rules. Contradicting match rules are
+    // either:
+    //  - a top-level rule and one of its sub-rules. E.g. variable and
+    //    variable(is_parameter).
+    //  - a sub-rule and a sibling that's negated. E.g.
+    //    variable(is_thread_local) and variable(unless(is_parameter))
+    llvm::SmallDenseMap<attr::SubjectMatchRule,
+                        std::pair<attr::SubjectMatchRule, SourceRange>, 2>
+        RulesToFirstSpecifiedNegatedSubRule;
+    for (const auto &Rule : Rules) {
+      Optional<attr::SubjectMatchRule> ParentRule =
+          getParentAttrMatcherRule(Rule.first);
+      if (!ParentRule)
+        continue;
+      auto It = Rules.find(*ParentRule);
+      if (It != Rules.end()) {
+        // A sub-rule contradicts a parent rule.
+        Diag(Rule.second.getBegin(),
+             diag::err_pragma_attribute_matcher_subrule_contradicts_rule)
+            << attr::getSubjectMatchRuleSpelling(Rule.first)
+            << attr::getSubjectMatchRuleSpelling(*ParentRule) << It->second
+            << FixItHint::CreateRemoval(
+                   replacementRangeForListElement(*this, Rule.second));
+        // Keep going without removing this rule as it won't change the set of
+        // declarations that receive the attribute.
+        continue;
+      }
+      if (isNegatedAttrMatcherSubRule(Rule.first))
+        RulesToFirstSpecifiedNegatedSubRule.insert(
+            std::make_pair(*ParentRule, Rule));
+    }
+    bool IgnoreNegatedSubRules = false;
+    for (const auto &Rule : Rules) {
+      Optional<attr::SubjectMatchRule> ParentRule =
+          getParentAttrMatcherRule(Rule.first);
+      if (!ParentRule)
+        continue;
+      auto It = RulesToFirstSpecifiedNegatedSubRule.find(*ParentRule);
+      if (It != RulesToFirstSpecifiedNegatedSubRule.end() &&
+          It->second != Rule) {
+        // Negated sub-rule contradicts another sub-rule.
+        Diag(
+            It->second.second.getBegin(),
+            diag::
+                err_pragma_attribute_matcher_negated_subrule_contradicts_subrule)
+            << attr::getSubjectMatchRuleSpelling(It->second.first)
+            << attr::getSubjectMatchRuleSpelling(Rule.first) << Rule.second
+            << FixItHint::CreateRemoval(
+                   replacementRangeForListElement(*this, It->second.second));
+        // Keep going but ignore all of the negated sub-rules.
+        IgnoreNegatedSubRules = true;
+        RulesToFirstSpecifiedNegatedSubRule.erase(It);
+      }
+    }
+
+    if (!IgnoreNegatedSubRules) {
+      for (const auto &Rule : Rules)
+        SubjectMatchRules.push_back(Rule.first);
+    } else {
+      for (const auto &Rule : Rules) {
+        if (!isNegatedAttrMatcherSubRule(Rule.first))
+          SubjectMatchRules.push_back(Rule.first);
+      }
+    }
+    Rules.clear();
+  } else {
+    for (const auto &Rule : StrictSubjectMatchRuleSet) {
+      if (Rules.erase(Rule.first)) {
+        // Add the rule to the set of attribute receivers only if it's supported
+        // in the current language mode.
+        if (Rule.second)
+          SubjectMatchRules.push_back(Rule.first);
+      }
+    }
+  }
+
+  if (!Rules.empty()) {
+    auto Diagnostic =
+        Diag(PragmaLoc, diag::err_pragma_attribute_invalid_matchers)
+        << Attribute.getName();
+    SmallVector<attr::SubjectMatchRule, 2> ExtraRules;
+    for (const auto &Rule : Rules) {
+      ExtraRules.push_back(Rule.first);
+      Diagnostic << FixItHint::CreateRemoval(
+          replacementRangeForListElement(*this, Rule.second));
+    }
+    Diagnostic << attrMatcherRuleListToString(ExtraRules);
+  }
+
+  PragmaAttributeStack.push_back(
+      {PragmaLoc, &Attribute, std::move(SubjectMatchRules), /*IsUsed=*/false});
+}
+
+void Sema::ActOnPragmaAttributePop(SourceLocation PragmaLoc) {
+  if (PragmaAttributeStack.empty()) {
+    Diag(PragmaLoc, diag::err_pragma_attribute_stack_mismatch);
+    return;
+  }
+  const PragmaAttributeEntry &Entry = PragmaAttributeStack.back();
+  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.pop_back();
+}
+
+void Sema::AddPragmaAttributes(Scope *S, Decl *D) {
+  if (PragmaAttributeStack.empty())
+    return;
+  for (auto &Entry : PragmaAttributeStack) {
+    const AttributeList *Attribute = Entry.Attribute;
+    assert(Attribute && "Expected an attribute");
+
+    // Ensure that the attribute can be applied to the given declaration.
+    bool Applies = false;
+    for (const auto &Rule : Entry.MatchRules) {
+      if (Attribute->appliesToDecl(D, Rule)) {
+        Applies = true;
+        break;
+      }
+    }
+    if (!Applies)
+      continue;
+    Entry.IsUsed = true;
+    assert(!Attribute->getNext() && "Expected just one attribute");
+    PragmaAttributeCurrentTargetDecl = D;
+    ProcessDeclAttributeList(S, D, Attribute);
+    PragmaAttributeCurrentTargetDecl = nullptr;
+  }
+}
+
+void Sema::PrintPragmaAttributeInstantiationPoint() {
+  assert(PragmaAttributeCurrentTargetDecl && "Expected an active declaration");
+  Diags.Report(PragmaAttributeCurrentTargetDecl->getLocStart(),
+               diag::note_pragma_attribute_applied_decl_here);
+}
+
+void Sema::DiagnoseUnterminatedPragmaAttribute() {
+  if (PragmaAttributeStack.empty())
+    return;
+  Diag(PragmaAttributeStack.back().Loc, diag::err_pragma_attribute_no_pop_eof);
+}
+
 void Sema::ActOnPragmaOptimize(bool On, SourceLocation PragmaLoc) {
   if(On)
     OptimizeOffPragmaLocation = SourceLocation();