diff --git a/clang/include/clang/Basic/AttrSubjectMatchRules.h b/clang/include/clang/Basic/AttrSubjectMatchRules.h --- a/clang/include/clang/Basic/AttrSubjectMatchRules.h +++ b/clang/include/clang/Basic/AttrSubjectMatchRules.h @@ -18,6 +18,9 @@ /// A list of all the recognized kinds of attributes. enum SubjectMatchRule { #define ATTR_MATCH_RULE(X, Spelling, IsAbstract) X, +#include "clang/Basic/AttrSubMatchRulesList.inc" + SubjectMatchRule_Last = -1 +#define ATTR_MATCH_RULE(X, Spelling, IsAbstract) +1 #include "clang/Basic/AttrSubMatchRulesList.inc" }; diff --git a/clang/include/clang/Basic/DiagnosticParseKinds.td b/clang/include/clang/Basic/DiagnosticParseKinds.td --- a/clang/include/clang/Basic/DiagnosticParseKinds.td +++ b/clang/include/clang/Basic/DiagnosticParseKinds.td @@ -1237,10 +1237,8 @@ "extra tokens after attribute in a '#pragma clang attribute push'">; def err_pragma_attribute_unsupported_attribute : Error< "attribute %0 is not supported by '#pragma clang attribute'">; -def err_pragma_attribute_multiple_attributes : Error< - "more than one attribute specified in '#pragma clang attribute push'">; def err_pragma_attribute_expected_attribute_syntax : Error< - "expected an attribute that is specified using the GNU, C++11 or '__declspec'" + "expected %select{|',' or }0an attribute that is specified using the GNU, C++11 or '__declspec'" " syntax">; def note_pragma_attribute_use_attribute_kw : Note<"use the GNU '__attribute__' " "syntax">; diff --git a/clang/lib/Parse/ParseDecl.cpp b/clang/lib/Parse/ParseDecl.cpp --- a/clang/lib/Parse/ParseDecl.cpp +++ b/clang/lib/Parse/ParseDecl.cpp @@ -600,6 +600,8 @@ bool Parser::ParseMicrosoftDeclSpecArgs(IdentifierInfo *AttrName, SourceLocation AttrNameLoc, ParsedAttributes &Attrs) { + unsigned ExistingAttrs = Attrs.size(); + // If the attribute isn't known, we will not attempt to parse any // arguments. if (!hasAttribute(AttrSyntax::Declspec, nullptr, AttrName, @@ -732,7 +734,7 @@ // If this attribute's args were parsed, and it was expected to have // arguments but none were provided, emit a diagnostic. - if (!Attrs.empty() && Attrs.begin()->getMaxArgs() && !NumArgs) { + if (ExistingAttrs < Attrs.size() && Attrs.back().getMaxArgs() && !NumArgs) { Diag(OpenParenLoc, diag::err_attribute_requires_arguments) << AttrName; return false; } diff --git a/clang/lib/Parse/ParsePragma.cpp b/clang/lib/Parse/ParsePragma.cpp --- a/clang/lib/Parse/ParsePragma.cpp +++ b/clang/lib/Parse/ParsePragma.cpp @@ -21,6 +21,7 @@ #include "clang/Parse/RAIIObjectsForParser.h" #include "clang/Sema/Scope.h" #include "llvm/ADT/ArrayRef.h" +#include "llvm/ADT/SmallSet.h" #include "llvm/ADT/StringSwitch.h" using namespace clang; @@ -1561,7 +1562,7 @@ /// suggests the possible attribute subject rules in a fix-it together with /// any other missing tokens. DiagnosticBuilder createExpectedAttributeSubjectRulesTokenDiagnostic( - unsigned DiagID, ParsedAttr &Attribute, + unsigned DiagID, ParsedAttributes &Attrs, MissingAttributeSubjectRulesRecoveryPoint Point, Parser &PRef) { SourceLocation Loc = PRef.getEndOfPreviousToken(); if (Loc.isInvalid()) @@ -1581,25 +1582,38 @@ SourceRange FixItRange(Loc); if (EndPoint == MissingAttributeSubjectRulesRecoveryPoint::None) { // Gather the subject match rules that are supported by the attribute. - SmallVector, 4> SubjectMatchRuleSet; - Attribute.getMatchRules(PRef.getLangOpts(), SubjectMatchRuleSet); - if (SubjectMatchRuleSet.empty()) { + // Add all the possible rules initially. + llvm::BitVector IsMatchRuleAvailable(attr::SubjectMatchRule_Last, true); + // Remove the ones that are not supported by any of the attributes. + for (const ParsedAttr &Attribute : Attrs) { + SmallVector, 4> MatchRules; + Attribute.getMatchRules(PRef.getLangOpts(), MatchRules); + llvm::BitVector IsSupported(attr::SubjectMatchRule_Last); + for (const auto &Rule : MatchRules) { + // Ensure that the missing rule is reported in the fix-it only when it's + // supported in the current language mode. + if (!Rule.second) + continue; + IsSupported[Rule.first] = true; + } + IsMatchRuleAvailable &= IsSupported; + } + if (IsMatchRuleAvailable.count() == 0) { // FIXME: We can emit a "fix-it" with a subject list placeholder when // placeholders will be supported by the fix-its. return Diagnostic; } FixIt += "any("; bool NeedsComma = false; - for (const auto &I : SubjectMatchRuleSet) { - // Ensure that the missing rule is reported in the fix-it only when it's - // supported in the current language mode. - if (!I.second) + for (unsigned I = 0; I < attr::SubjectMatchRule_Last; I++) { + if (!IsMatchRuleAvailable[I]) continue; if (NeedsComma) FixIt += ", "; else NeedsComma = true; - FixIt += attr::getSubjectMatchRuleSpelling(I.first); + FixIt += attr::getSubjectMatchRuleSpelling( + static_cast(I)); } FixIt += ")"; // Check if we need to remove the range @@ -1649,99 +1663,104 @@ ConsumeToken(); }; - if (Tok.is(tok::l_square) && NextToken().is(tok::l_square)) { - // Parse the CXX11 style attribute. - ParseCXX11AttributeSpecifier(Attrs); - } else if (Tok.is(tok::kw___attribute)) { - ConsumeToken(); - if (ExpectAndConsume(tok::l_paren, diag::err_expected_lparen_after, - "attribute")) - return SkipToEnd(); - if (ExpectAndConsume(tok::l_paren, diag::err_expected_lparen_after, "(")) - return SkipToEnd(); + auto IsCXX11Attribute = [&] { + return Tok.is(tok::l_square) && NextToken().is(tok::l_square); + }; + auto IsClangAttribute = [&] { return Tok.is(tok::kw___attribute); }; + auto IsMicrosoftAttribute = [&] { return Tok.is(tok::kw___declspec); }; - // FIXME: The practical usefulness of completion here is limited because - // we only get here if the line has balanced parens. - if (Tok.is(tok::code_completion)) { - cutOffParsing(); - // FIXME: suppress completion of unsupported attributes? - Actions.CodeCompleteAttribute(AttributeCommonInfo::Syntax::AS_GNU); - return SkipToEnd(); - } + do { + if (IsCXX11Attribute()) { + // Parse the CXX11 style attribute. + ParseCXX11AttributeSpecifier(Attrs); + } else if (IsClangAttribute()) { + ConsumeToken(); + if (ExpectAndConsume(tok::l_paren, diag::err_expected_lparen_after, + "attribute")) + return SkipToEnd(); + if (ExpectAndConsume(tok::l_paren, diag::err_expected_lparen_after, "(")) + return SkipToEnd(); + + // FIXME: The practical usefulness of completion here is limited because + // we only get here if the line has balanced parens. + if (Tok.is(tok::code_completion)) { + cutOffParsing(); + // FIXME: suppress completion of unsupported attributes? + Actions.CodeCompleteAttribute(AttributeCommonInfo::Syntax::AS_GNU); + return SkipToEnd(); + } - if (Tok.isNot(tok::identifier)) { - Diag(Tok, diag::err_pragma_attribute_expected_attribute_name); - SkipToEnd(); - return; - } - IdentifierInfo *AttrName = Tok.getIdentifierInfo(); - SourceLocation AttrNameLoc = ConsumeToken(); + if (Tok.isNot(tok::identifier)) { + Diag(Tok, diag::err_pragma_attribute_expected_attribute_name); + SkipToEnd(); + return; + } + IdentifierInfo *AttrName = Tok.getIdentifierInfo(); + SourceLocation AttrNameLoc = ConsumeToken(); - if (Tok.isNot(tok::l_paren)) - Attrs.addNew(AttrName, AttrNameLoc, nullptr, AttrNameLoc, nullptr, 0, - ParsedAttr::AS_GNU); - else - ParseGNUAttributeArgs(AttrName, AttrNameLoc, Attrs, /*EndLoc=*/nullptr, - /*ScopeName=*/nullptr, - /*ScopeLoc=*/SourceLocation(), ParsedAttr::AS_GNU, - /*Declarator=*/nullptr); - - if (ExpectAndConsume(tok::r_paren)) - return SkipToEnd(); - if (ExpectAndConsume(tok::r_paren)) - return SkipToEnd(); - } else if (Tok.is(tok::kw___declspec)) { - ParseMicrosoftDeclSpecs(Attrs); - } else { - Diag(Tok, diag::err_pragma_attribute_expected_attribute_syntax); - if (Tok.getIdentifierInfo()) { - // If we suspect that this is an attribute suggest the use of - // '__attribute__'. - if (ParsedAttr::getParsedKind( - Tok.getIdentifierInfo(), /*ScopeName=*/nullptr, - ParsedAttr::AS_GNU) != ParsedAttr::UnknownAttribute) { - SourceLocation InsertStartLoc = Tok.getLocation(); - ConsumeToken(); - if (Tok.is(tok::l_paren)) { - ConsumeAnyToken(); - SkipUntil(tok::r_paren, StopBeforeMatch); - if (Tok.isNot(tok::r_paren)) - return SkipToEnd(); + if (Tok.isNot(tok::l_paren)) + Attrs.addNew(AttrName, AttrNameLoc, nullptr, AttrNameLoc, nullptr, 0, + ParsedAttr::AS_GNU); + else + ParseGNUAttributeArgs(AttrName, AttrNameLoc, Attrs, /*EndLoc=*/nullptr, + /*ScopeName=*/nullptr, + /*ScopeLoc=*/SourceLocation(), ParsedAttr::AS_GNU, + /*Declarator=*/nullptr); + + if (ExpectAndConsume(tok::r_paren)) + return SkipToEnd(); + if (ExpectAndConsume(tok::r_paren)) + return SkipToEnd(); + } else if (IsMicrosoftAttribute()) { + ParseMicrosoftDeclSpecs(Attrs); + } else { + Diag(Tok, diag::err_pragma_attribute_expected_attribute_syntax) + << !Attrs.empty(); + if (Tok.getIdentifierInfo()) { + // If we suspect that this is an attribute suggest the use of + // '__attribute__'. + if (ParsedAttr::getParsedKind( + Tok.getIdentifierInfo(), /*ScopeName=*/nullptr, + ParsedAttr::AS_GNU) != ParsedAttr::UnknownAttribute) { + SourceLocation InsertStartLoc = Tok.getLocation(); + ConsumeToken(); + if (Tok.is(tok::l_paren)) { + ConsumeAnyToken(); + SkipUntil(tok::r_paren, StopBeforeMatch); + if (Tok.isNot(tok::r_paren)) + return SkipToEnd(); + } + Diag(Tok, diag::note_pragma_attribute_use_attribute_kw) + << FixItHint::CreateInsertion(InsertStartLoc, "__attribute__((") + << FixItHint::CreateInsertion(Tok.getEndLoc(), "))"); } - Diag(Tok, diag::note_pragma_attribute_use_attribute_kw) - << FixItHint::CreateInsertion(InsertStartLoc, "__attribute__((") - << FixItHint::CreateInsertion(Tok.getEndLoc(), "))"); } + SkipToEnd(); + return; } - SkipToEnd(); - return; - } - - if (Attrs.empty() || Attrs.begin()->isInvalid()) { - SkipToEnd(); - return; - } - // Ensure that we don't have more than one attribute. - if (Attrs.size() > 1) { - SourceLocation Loc = Attrs[1].getLoc(); - Diag(Loc, diag::err_pragma_attribute_multiple_attributes); - SkipToEnd(); - return; - } + if (Attrs.empty() || Attrs.begin()->isInvalid()) { + SkipToEnd(); + return; + } + } while (IsCXX11Attribute() || IsClangAttribute() || IsMicrosoftAttribute() || + (Tok.getIdentifierInfo() && + !Tok.getIdentifierInfo()->isStr("apply_to") && + !Tok.getIdentifierInfo()->isStr("any"))); - ParsedAttr &Attribute = *Attrs.begin(); - if (!Attribute.isSupportedByPragmaAttribute()) { - Diag(PragmaLoc, diag::err_pragma_attribute_unsupported_attribute) - << Attribute; - SkipToEnd(); - return; + for (const ParsedAttr &Attribute : Attrs) { + if (!Attribute.isSupportedByPragmaAttribute()) { + Diag(PragmaLoc, diag::err_pragma_attribute_unsupported_attribute) + << Attribute; + SkipToEnd(); + return; + } } // Parse the subject-list. if (!TryConsumeToken(tok::comma)) { createExpectedAttributeSubjectRulesTokenDiagnostic( - diag::err_expected, Attribute, + diag::err_expected, Attrs, MissingAttributeSubjectRulesRecoveryPoint::Comma, *this) << tok::comma; SkipToEnd(); @@ -1750,7 +1769,7 @@ if (Tok.isNot(tok::identifier)) { createExpectedAttributeSubjectRulesTokenDiagnostic( - diag::err_pragma_attribute_invalid_subject_set_specifier, Attribute, + diag::err_pragma_attribute_invalid_subject_set_specifier, Attrs, MissingAttributeSubjectRulesRecoveryPoint::ApplyTo, *this); SkipToEnd(); return; @@ -1758,7 +1777,7 @@ const IdentifierInfo *II = Tok.getIdentifierInfo(); if (!II->isStr("apply_to")) { createExpectedAttributeSubjectRulesTokenDiagnostic( - diag::err_pragma_attribute_invalid_subject_set_specifier, Attribute, + diag::err_pragma_attribute_invalid_subject_set_specifier, Attrs, MissingAttributeSubjectRulesRecoveryPoint::ApplyTo, *this); SkipToEnd(); return; @@ -1767,7 +1786,7 @@ if (!TryConsumeToken(tok::equal)) { createExpectedAttributeSubjectRulesTokenDiagnostic( - diag::err_expected, Attribute, + diag::err_expected, Attrs, MissingAttributeSubjectRulesRecoveryPoint::Equals, *this) << tok::equal; SkipToEnd(); @@ -1797,8 +1816,10 @@ if (Info->Action == PragmaAttributeInfo::Push) Actions.ActOnPragmaAttributeEmptyPush(PragmaLoc, Info->Namespace); - Actions.ActOnPragmaAttributeAttribute(Attribute, PragmaLoc, - std::move(SubjectMatchRules)); + for (ParsedAttr &Attribute : Attrs) { + Actions.ActOnPragmaAttributeAttribute(Attribute, PragmaLoc, + SubjectMatchRules); + } } // #pragma GCC visibility comes in two variants: diff --git a/clang/test/AST/pragma-multiple-attributes-declspec.cpp b/clang/test/AST/pragma-multiple-attributes-declspec.cpp new file mode 100644 --- /dev/null +++ b/clang/test/AST/pragma-multiple-attributes-declspec.cpp @@ -0,0 +1,15 @@ +// RUN: %clang_cc1 -triple i386-pc-win32 -fms-extensions -fms-compatibility -fsyntax-only -ast-dump %s | FileCheck %s + +#pragma clang attribute push (__declspec(dllexport) [[clang::annotate("test1")]], apply_to=variable(is_global)) +int var1; +#pragma clang attribute pop +// CHECK: VarDecl {{.*}} var1 +// CHECK-NEXT: DLLExportAttr {{.*}} +// CHECK-NEXT: AnnotateAttr {{.*}} "test1" + +#pragma clang attribute push (__declspec(dllexport, dllimport) [[clang::annotate("test2")]], apply_to=function) +void func2(); +#pragma clang attribute pop +// CHECK: FunctionDecl {{.*}} func2 +// CHECK-NEXT: DLLExportAttr {{.*}} +// CHECK-NEXT: AnnotateAttr {{.*}} "test2" diff --git a/clang/test/AST/pragma-multiple-attributes.cpp b/clang/test/AST/pragma-multiple-attributes.cpp new file mode 100644 --- /dev/null +++ b/clang/test/AST/pragma-multiple-attributes.cpp @@ -0,0 +1,22 @@ +// RUN: %clang_cc1 -fsyntax-only -ast-dump %s | FileCheck %s + +#pragma clang attribute push (__attribute__((disable_sanitizer_instrumentation)) __attribute__((annotate("test1"))), apply_to=variable(is_global)) +int var1; +#pragma clang attribute pop +// CHECK: VarDecl {{.*}} var1 +// CHECK-NEXT: DisableSanitizerInstrumentationAttr {{.*}} +// CHECK-NEXT: AnnotateAttr {{.*}} "test1" + +#pragma clang attribute push (__attribute__((disable_sanitizer_instrumentation)) [[clang::annotate("test2")]], apply_to=variable(is_global)) +int var2; +#pragma clang attribute pop +// CHECK: VarDecl {{.*}} var2 +// CHECK-NEXT: DisableSanitizerInstrumentationAttr {{.*}} +// CHECK-NEXT: AnnotateAttr {{.*}} "test2" + +#pragma clang attribute push ([[clang::disable_sanitizer_instrumentation]] [[clang::annotate("test3")]], apply_to=variable(is_global)) +int var3; +#pragma clang attribute pop +// CHECK: VarDecl {{.*}} var3 +// CHECK-NEXT: DisableSanitizerInstrumentationAttr {{.*}} +// CHECK-NEXT: AnnotateAttr {{.*}} "test3" diff --git a/clang/test/FixIt/fixit-pragma-attribute.c b/clang/test/FixIt/fixit-pragma-attribute.c --- a/clang/test/FixIt/fixit-pragma-attribute.c +++ b/clang/test/FixIt/fixit-pragma-attribute.c @@ -3,4 +3,4 @@ // rules that are not applicable in the current language mode. #pragma clang attribute push (__attribute__((abi_tag("a")))) -// CHECK: fix-it:{{.*}}:{[[@LINE-1]]:60-[[@LINE-1]]:60}:", apply_to = any(record(unless(is_union)), variable, function)" +// CHECK: fix-it:{{.*}}:{[[@LINE-1]]:60-[[@LINE-1]]:60}:", apply_to = any(function, record(unless(is_union)), variable)" diff --git a/clang/test/FixIt/fixit-pragma-attribute.cpp b/clang/test/FixIt/fixit-pragma-attribute.cpp --- a/clang/test/FixIt/fixit-pragma-attribute.cpp +++ b/clang/test/FixIt/fixit-pragma-attribute.cpp @@ -39,7 +39,7 @@ #pragma clang attribute pop #pragma clang attribute push (__attribute__((abi_tag("a")))) -// CHECK: fix-it:{{.*}}:{[[@LINE-1]]:60-[[@LINE-1]]:60}:", apply_to = any(record(unless(is_union)), variable, function, namespace)" +// CHECK: fix-it:{{.*}}:{[[@LINE-1]]:60-[[@LINE-1]]:60}:", apply_to = any(function, namespace, record(unless(is_union)), variable)" #pragma clang attribute push (__attribute__((abi_tag("a"))) apply_to=function) // CHECK: fix-it:{{.*}}:{[[@LINE-1]]:60-[[@LINE-1]]:60}:", " #pragma clang attribute push (__attribute__((abi_tag("a"))) = function) @@ -48,35 +48,33 @@ // CHECK: fix-it:{{.*}}:{[[@LINE-1]]:60-[[@LINE-1]]:60}:", apply_to = " #pragma clang attribute push (__attribute__((abi_tag("a"))) 22) -// CHECK: fix-it:{{.*}}:{[[@LINE-1]]:60-[[@LINE-1]]:63}:", apply_to = any(record(unless(is_union)), variable, function, namespace)" -#pragma clang attribute push (__attribute__((abi_tag("a"))) function) -// CHECK: fix-it:{{.*}}:{[[@LINE-1]]:60-[[@LINE-1]]:69}:", apply_to = any(record(unless(is_union)), variable, function, namespace)" +// CHECK: fix-it:{{.*}}:{[[@LINE-1]]:60-[[@LINE-1]]:63}:", apply_to = any(function, namespace, record(unless(is_union)), variable)" #pragma clang attribute push (__attribute__((abi_tag("a"))) (function)) -// CHECK: fix-it:{{.*}}:{[[@LINE-1]]:60-[[@LINE-1]]:71}:", apply_to = any(record(unless(is_union)), variable, function, namespace)" +// CHECK: fix-it:{{.*}}:{[[@LINE-1]]:60-[[@LINE-1]]:71}:", apply_to = any(function, namespace, record(unless(is_union)), variable)" #pragma clang attribute push (__attribute__((abi_tag("a"))), ) -// CHECK: fix-it:{{.*}}:{[[@LINE-1]]:61-[[@LINE-1]]:62}:"apply_to = any(record(unless(is_union)), variable, function, namespace)" +// CHECK: fix-it:{{.*}}:{[[@LINE-1]]:61-[[@LINE-1]]:62}:"apply_to = any(function, namespace, record(unless(is_union)), variable)" #pragma clang attribute push (__attribute__((abi_tag("a"))), = function) // CHECK: fix-it:{{.*}}:{[[@LINE-1]]:61-[[@LINE-1]]:61}:"apply_to" #pragma clang attribute push (__attribute__((abi_tag("a"))), any(function)) // CHECK: fix-it:{{.*}}:{[[@LINE-1]]:61-[[@LINE-1]]:61}:"apply_to = " #pragma clang attribute push (__attribute__((abi_tag("a"))), 22) -// CHECK: fix-it:{{.*}}:{[[@LINE-1]]:61-[[@LINE-1]]:64}:"apply_to = any(record(unless(is_union)), variable, function, namespace)" +// CHECK: fix-it:{{.*}}:{[[@LINE-1]]:61-[[@LINE-1]]:64}:"apply_to = any(function, namespace, record(unless(is_union)), variable)" #pragma clang attribute push (__attribute__((abi_tag("a"))), 1, 2) -// CHECK: fix-it:{{.*}}:{[[@LINE-1]]:61-[[@LINE-1]]:66}:"apply_to = any(record(unless(is_union)), variable, function, namespace)" +// CHECK: fix-it:{{.*}}:{[[@LINE-1]]:61-[[@LINE-1]]:66}:"apply_to = any(function, namespace, record(unless(is_union)), variable)" #pragma clang attribute push (__attribute__((abi_tag("a"))), function) -// CHECK: fix-it:{{.*}}:{[[@LINE-1]]:61-[[@LINE-1]]:70}:"apply_to = any(record(unless(is_union)), variable, function, namespace)" +// CHECK: fix-it:{{.*}}:{[[@LINE-1]]:61-[[@LINE-1]]:70}:"apply_to = any(function, namespace, record(unless(is_union)), variable)" #pragma clang attribute push (__attribute__((abi_tag("a"))), (function)) -// CHECK: fix-it:{{.*}}:{[[@LINE-1]]:61-[[@LINE-1]]:72}:"apply_to = any(record(unless(is_union)), variable, function, namespace)" +// CHECK: fix-it:{{.*}}:{[[@LINE-1]]:61-[[@LINE-1]]:72}:"apply_to = any(function, namespace, record(unless(is_union)), variable)" #pragma clang attribute push (__attribute__((abi_tag("a"))), apply_to) -// CHECK: fix-it:{{.*}}:{[[@LINE-1]]:70-[[@LINE-1]]:70}:" = any(record(unless(is_union)), variable, function, namespace)" +// CHECK: fix-it:{{.*}}:{[[@LINE-1]]:70-[[@LINE-1]]:70}:" = any(function, namespace, record(unless(is_union)), variable)" #pragma clang attribute push (__attribute__((abi_tag("a"))), apply_to any(function)) // CHECK: fix-it:{{.*}}:{[[@LINE-1]]:70-[[@LINE-1]]:70}:" = " #pragma clang attribute push (__attribute__((abi_tag("a"))), apply_to 41 (22)) -// CHECK: fix-it:{{.*}}:{[[@LINE-1]]:70-[[@LINE-1]]:78}:" = any(record(unless(is_union)), variable, function, namespace)" +// CHECK: fix-it:{{.*}}:{[[@LINE-1]]:70-[[@LINE-1]]:78}:" = any(function, namespace, record(unless(is_union)), variable)" // Don't give fix-it to attributes without a strict subject set #pragma clang attribute push (__attribute__((annotate("a")))) diff --git a/clang/test/Parser/pragma-attribute-declspec.cpp b/clang/test/Parser/pragma-attribute-declspec.cpp --- a/clang/test/Parser/pragma-attribute-declspec.cpp +++ b/clang/test/Parser/pragma-attribute-declspec.cpp @@ -6,7 +6,8 @@ #pragma clang attribute pop -#pragma clang attribute push(__declspec(dllexport, dllimport), apply_to = function) // expected-error {{more than one attribute specified in '#pragma clang attribute push'}} +#pragma clang attribute push(__declspec(dllexport, dllimport), apply_to = function) +#pragma clang attribute pop #pragma clang attribute push(__declspec(align), apply_to = variable) // expected-error {{attribute 'align' is not supported by '#pragma clang attribute'}} diff --git a/clang/test/Parser/pragma-attribute.cpp b/clang/test/Parser/pragma-attribute.cpp --- a/clang/test/Parser/pragma-attribute.cpp +++ b/clang/test/Parser/pragma-attribute.cpp @@ -154,9 +154,6 @@ #pragma clang attribute push ([[gnu::abi_tag]], apply_to=any(function)) #pragma clang attribute pop -#pragma clang attribute push ([[clang::disable_tail_calls, noreturn]], apply_to = function) // expected-error {{more than one attribute specified in '#pragma clang attribute push'}} -#pragma clang attribute push ([[clang::disable_tail_calls, noreturn]]) // expected-error {{more than one attribute specified in '#pragma clang attribute push'}} - #pragma clang attribute push ([[gnu::abi_tag]], apply_to=namespace) #pragma clang attribute pop diff --git a/clang/test/Parser/pragma-multiple-attributes.cpp b/clang/test/Parser/pragma-multiple-attributes.cpp new file mode 100644 --- /dev/null +++ b/clang/test/Parser/pragma-multiple-attributes.cpp @@ -0,0 +1,16 @@ +// RUN: %clang_cc1 -Wno-pragma-clang-attribute -verify %s + +#pragma clang attribute push ([[clang::disable_tail_calls, noreturn]], apply_to = function) +#pragma clang attribute pop + +#pragma clang attribute push ([[clang::disable_tail_calls]] __attribute__((annotate("test"))), apply_to = function) +#pragma clang attribute pop + +#pragma clang attribute push (__attribute__((disable_tail_calls)) __attribute__((annotate("test"))), apply_to = function) +#pragma clang attribute pop + +#pragma clang attribute push ([[clang::disable_tail_calls, noreturn]]) // expected-error {{expected ','}} + +#pragma clang attribute push (test [[noreturn]]) // expected-error {{expected an attribute that is specified using the GNU, C++11 or '__declspec' syntax}} + +#pragma clang attribute push ([[noreturn]] test) // expected-error {{expected ',' or an attribute that is specified using the GNU, C++11 or '__declspec' syntax}}