[clang] [BoundsSafety] WIP: Make 'counted_by' work for pointer fields; late parsing for 'counted_by' on decl attr position (PR #87596)
Dan Liew via cfe-commits
cfe-commits at lists.llvm.org
Fri Apr 12 16:26:39 PDT 2024
https://github.com/delcypher updated https://github.com/llvm/llvm-project/pull/87596
>From af735f3216dd5db9dcaf164892f3f573731701ec Mon Sep 17 00:00:00 2001
From: Yeoul Na <yeoul_na at apple.com>
Date: Wed, 3 Apr 2024 20:58:46 -0700
Subject: [PATCH 1/3] [BoundsSafety] WIP: Make 'counted_by' work for pointer
fields; late parsing for 'counted_by' on decl attr position
---
clang/include/clang/Basic/Attr.td | 3 +-
clang/include/clang/Parse/Parser.h | 11 +-
clang/include/clang/Sema/Sema.h | 2 +-
clang/lib/Parse/ParseDecl.cpp | 108 ++++++++++++++++--
clang/lib/Parse/ParseObjc.cpp | 6 +-
clang/lib/Sema/SemaDeclAttr.cpp | 11 +-
clang/lib/Sema/SemaType.cpp | 6 +-
clang/lib/Sema/TreeTransform.h | 2 +-
clang/test/AST/attr-counted-by-late-parsed.c | 29 +++++
clang/test/Sema/attr-counted-by-late-parsed.c | 54 +++++++++
clang/test/Sema/attr-counted-by.c | 2 +-
11 files changed, 212 insertions(+), 22 deletions(-)
create mode 100644 clang/test/AST/attr-counted-by-late-parsed.c
create mode 100644 clang/test/Sema/attr-counted-by-late-parsed.c
diff --git a/clang/include/clang/Basic/Attr.td b/clang/include/clang/Basic/Attr.td
index 6584460cf5685e..d6caa28938edef 100644
--- a/clang/include/clang/Basic/Attr.td
+++ b/clang/include/clang/Basic/Attr.td
@@ -2197,7 +2197,8 @@ def TypeNullUnspecified : TypeAttr {
def CountedBy : DeclOrTypeAttr {
let Spellings = [Clang<"counted_by">];
let Subjects = SubjectList<[Field], ErrorDiag>;
- let Args = [ExprArgument<"Count">, IntArgument<"NestedLevel">];
+ let Args = [ExprArgument<"Count">, IntArgument<"NestedLevel", 1>];
+ let LateParsed = 1;
let ParseArgumentsAsUnevaluated = 1;
let Documentation = [CountedByDocs];
let LangOpts = [COnly];
diff --git a/clang/include/clang/Parse/Parser.h b/clang/include/clang/Parse/Parser.h
index 580bf2a5d79df5..41fc5d5dc98a6b 100644
--- a/clang/include/clang/Parse/Parser.h
+++ b/clang/include/clang/Parse/Parser.h
@@ -1606,6 +1606,10 @@ class Parser : public CodeCompletionHandler {
bool EnterScope, bool OnDefinition);
void ParseLexedAttribute(LateParsedAttribute &LA,
bool EnterScope, bool OnDefinition);
+ void ParseLexedCAttributeList(LateParsedAttrList &LA, bool EnterScope,
+ ParsedAttributes *OutAttrs = nullptr);
+ void ParseLexedCAttribute(LateParsedAttribute &LA, bool EnterScope,
+ ParsedAttributes *OutAttrs = nullptr);
void ParseLexedMethodDeclarations(ParsingClass &Class);
void ParseLexedMethodDeclaration(LateParsedMethodDeclaration &LM);
void ParseLexedMethodDefs(ParsingClass &Class);
@@ -2497,7 +2501,9 @@ class Parser : public CodeCompletionHandler {
void ParseStructDeclaration(
ParsingDeclSpec &DS,
- llvm::function_ref<void(ParsingFieldDeclarator &)> FieldsCallback);
+ llvm::function_ref<void(ParsingFieldDeclarator &, Decl *&)>
+ FieldsCallback,
+ LateParsedAttrList *LateFieldAttrs = nullptr);
DeclGroupPtrTy ParseTopLevelStmtDecl();
@@ -3074,6 +3080,9 @@ class Parser : public CodeCompletionHandler {
SourceLocation ScopeLoc,
ParsedAttr::Form Form);
+ void DistributeCLateParsedAttrs(Declarator &D, Decl *Dcl,
+ LateParsedAttrList *LateAttrs);
+
void ParseBoundsAttribute(IdentifierInfo &AttrName,
SourceLocation AttrNameLoc, ParsedAttributes &Attrs,
IdentifierInfo *ScopeName, SourceLocation ScopeLoc,
diff --git a/clang/include/clang/Sema/Sema.h b/clang/include/clang/Sema/Sema.h
index 8c98d8c7fef7a7..de49f2344f017f 100644
--- a/clang/include/clang/Sema/Sema.h
+++ b/clang/include/clang/Sema/Sema.h
@@ -11761,7 +11761,7 @@ class Sema final {
QualType BuildMatrixType(QualType T, Expr *NumRows, Expr *NumColumns,
SourceLocation AttrLoc);
- QualType BuildCountAttributedArrayType(QualType WrappedTy, Expr *CountExpr);
+ QualType BuildCountAttributedArrayOrPointerType(QualType WrappedTy, Expr *CountExpr);
QualType BuildAddressSpaceAttr(QualType &T, LangAS ASIdx, Expr *AddrSpace,
SourceLocation AttrLoc);
diff --git a/clang/lib/Parse/ParseDecl.cpp b/clang/lib/Parse/ParseDecl.cpp
index 0aa14b0510746b..1c2e6d447cf5d5 100644
--- a/clang/lib/Parse/ParseDecl.cpp
+++ b/clang/lib/Parse/ParseDecl.cpp
@@ -3254,6 +3254,17 @@ void Parser::ParseAlignmentSpecifier(ParsedAttributes &Attrs,
}
}
+void Parser::DistributeCLateParsedAttrs(Declarator &D, Decl *Dcl,
+ LateParsedAttrList *LateAttrs) {
+ if (!LateAttrs)
+ return;
+
+ for (auto *LateAttr : *LateAttrs) {
+ if (LateAttr->Decls.empty())
+ LateAttr->addDecl(Dcl);
+ }
+}
+
/// Bounds attributes (e.g., counted_by):
/// AttrName '(' expression ')'
void Parser::ParseBoundsAttribute(IdentifierInfo &AttrName,
@@ -4787,13 +4798,14 @@ static void DiagnoseCountAttributedTypeInUnnamedAnon(ParsingDeclSpec &DS,
///
void Parser::ParseStructDeclaration(
ParsingDeclSpec &DS,
- llvm::function_ref<void(ParsingFieldDeclarator &)> FieldsCallback) {
+ llvm::function_ref<void(ParsingFieldDeclarator &, Decl *&)> FieldsCallback,
+ LateParsedAttrList *LateFieldAttrs) {
if (Tok.is(tok::kw___extension__)) {
// __extension__ silences extension warnings in the subexpression.
ExtensionRAIIObject O(Diags); // Use RAII to do this.
ConsumeToken();
- return ParseStructDeclaration(DS, FieldsCallback);
+ return ParseStructDeclaration(DS, FieldsCallback, LateFieldAttrs);
}
// Parse leading attributes.
@@ -4858,10 +4870,12 @@ void Parser::ParseStructDeclaration(
}
// If attributes exist after the declarator, parse them.
- MaybeParseGNUAttributes(DeclaratorInfo.D);
+ MaybeParseGNUAttributes(DeclaratorInfo.D, LateFieldAttrs);
// We're done with this declarator; invoke the callback.
- FieldsCallback(DeclaratorInfo);
+ Decl *Field = nullptr;
+ FieldsCallback(DeclaratorInfo, Field);
+ DistributeCLateParsedAttrs(DeclaratorInfo.D, Field, LateFieldAttrs);
// If we don't have a comma, it is either the end of the list (a ';')
// or an error, bail out.
@@ -4872,6 +4886,79 @@ void Parser::ParseStructDeclaration(
}
}
+// Parse all attributes in LA, and attach them to Decl D.
+void Parser::ParseLexedCAttributeList(LateParsedAttrList &LA, bool EnterScope, ParsedAttributes *OutAttrs) {
+ assert(LA.parseSoon() &&
+ "Attribute list should be marked for immediate parsing.");
+ for (unsigned i = 0, ni = LA.size(); i < ni; ++i) {
+ ParseLexedCAttribute(*LA[i], EnterScope, OutAttrs);
+ delete LA[i];
+ }
+ LA.clear();
+}
+
+/// Finish parsing an attribute for which parsing was delayed.
+/// This will be called at the end of parsing a class declaration
+/// for each LateParsedAttribute. We consume the saved tokens and
+/// create an attribute with the arguments filled in. We add this
+/// to the Attribute list for the decl.
+void Parser::ParseLexedCAttribute(LateParsedAttribute &LA, bool EnterScope, ParsedAttributes *OutAttrs) {
+ // Create a fake EOF so that attribute parsing won't go off the end of the
+ // attribute.
+ Token AttrEnd;
+ AttrEnd.startToken();
+ AttrEnd.setKind(tok::eof);
+ AttrEnd.setLocation(Tok.getLocation());
+ AttrEnd.setEofData(LA.Toks.data());
+ LA.Toks.push_back(AttrEnd);
+
+ // Append the current token at the end of the new token stream so that it
+ // doesn't get lost.
+ LA.Toks.push_back(Tok);
+ PP.EnterTokenStream(LA.Toks, true, /*IsReinject=*/true);
+ // Consume the previously pushed token.
+ ConsumeAnyToken(/*ConsumeCodeCompletionTok=*/true);
+
+ ParsedAttributes Attrs(AttrFactory);
+
+ assert(
+ LA.Decls.size() < 2 &&
+ "late field attribute expects to have at most a single declaration.");
+
+ Decl *D = LA.Decls.empty() ? nullptr : LA.Decls[0];
+
+ // If the Decl is on a function, add function parameters to the scope.
+ {
+ std::unique_ptr<ParseScope> Scope;
+ EnterScope &= D && D->isFunctionOrFunctionTemplate();
+ if (EnterScope) {
+ Scope.reset(new ParseScope(this, Scope::FnScope | Scope::DeclScope));
+ Actions.ActOnReenterFunctionContext(Actions.CurScope, D);
+ }
+ ParseBoundsAttribute(LA.AttrName, LA.AttrNameLoc, Attrs,
+ /*ScopeName*/nullptr, SourceLocation(),
+ ParsedAttr::Form::GNU());
+ if (EnterScope) {
+ Actions.ActOnExitFunctionContext();
+ }
+ }
+
+ for (unsigned i = 0, ni = LA.Decls.size(); i < ni; ++i)
+ Actions.ActOnFinishDelayedAttribute(getCurScope(), LA.Decls[i], Attrs);
+
+ // Due to a parsing error, we either went over the cached tokens or
+ // there are still cached tokens left, so we skip the leftover tokens.
+ while (Tok.isNot(tok::eof))
+ ConsumeAnyToken();
+
+ if (Tok.is(tok::eof) && Tok.getEofData() == AttrEnd.getEofData())
+ ConsumeAnyToken();
+
+ if (OutAttrs) {
+ OutAttrs->takeAllFrom(Attrs);
+ }
+}
+
/// ParseStructUnionBody
/// struct-contents:
/// struct-declaration-list
@@ -4895,6 +4982,8 @@ void Parser::ParseStructUnionBody(SourceLocation RecordLoc,
ParseScope StructScope(this, Scope::ClassScope|Scope::DeclScope);
Actions.ActOnTagStartDefinition(getCurScope(), TagDecl);
+ LateParsedAttrList LateFieldAttrs;
+
// While we still have something to read, read the declarations in the struct.
while (!tryParseMisplacedModuleImport() && Tok.isNot(tok::r_brace) &&
Tok.isNot(tok::eof)) {
@@ -4945,18 +5034,19 @@ void Parser::ParseStructUnionBody(SourceLocation RecordLoc,
}
if (!Tok.is(tok::at)) {
- auto CFieldCallback = [&](ParsingFieldDeclarator &FD) {
+ auto CFieldCallback = [&](ParsingFieldDeclarator &FD, Decl *&Dcl) {
// Install the declarator into the current TagDecl.
Decl *Field =
Actions.ActOnField(getCurScope(), TagDecl,
FD.D.getDeclSpec().getSourceRange().getBegin(),
FD.D, FD.BitfieldSize);
FD.complete(Field);
+ Dcl = Field;
};
// Parse all the comma separated declarators.
ParsingDeclSpec DS(*this);
- ParseStructDeclaration(DS, CFieldCallback);
+ ParseStructDeclaration(DS, CFieldCallback, &LateFieldAttrs);
} else { // Handle @defs
ConsumeToken();
if (!Tok.isObjCAtKeyword(tok::objc_defs)) {
@@ -4997,7 +5087,11 @@ void Parser::ParseStructUnionBody(SourceLocation RecordLoc,
ParsedAttributes attrs(AttrFactory);
// If attributes exist after struct contents, parse them.
- MaybeParseGNUAttributes(attrs);
+ MaybeParseGNUAttributes(attrs, &LateFieldAttrs);
+
+ assert(!getLangOpts().CPlusPlus);
+ for (auto LateAttr : LateFieldAttrs)
+ ParseLexedCAttribute(*LateAttr, true);
SmallVector<Decl *, 32> FieldDecls(TagDecl->fields());
diff --git a/clang/lib/Parse/ParseObjc.cpp b/clang/lib/Parse/ParseObjc.cpp
index 88bab0eb27a3ed..08cdd45e633ea1 100644
--- a/clang/lib/Parse/ParseObjc.cpp
+++ b/clang/lib/Parse/ParseObjc.cpp
@@ -778,7 +778,7 @@ void Parser::ParseObjCInterfaceDeclList(tok::ObjCKeywordKind contextKey,
}
bool addedToDeclSpec = false;
- auto ObjCPropertyCallback = [&](ParsingFieldDeclarator &FD) {
+ auto ObjCPropertyCallback = [&](ParsingFieldDeclarator &FD, Decl *&Dcl) {
if (FD.D.getIdentifier() == nullptr) {
Diag(AtLoc, diag::err_objc_property_requires_field_name)
<< FD.D.getSourceRange();
@@ -816,6 +816,7 @@ void Parser::ParseObjCInterfaceDeclList(tok::ObjCKeywordKind contextKey,
MethodImplKind);
FD.complete(Property);
+ Dcl = Property;
};
// Parse all the comma separated declarators.
@@ -2024,7 +2025,7 @@ void Parser::ParseObjCClassInstanceVariables(ObjCContainerDecl *interfaceDecl,
continue;
}
- auto ObjCIvarCallback = [&](ParsingFieldDeclarator &FD) {
+ auto ObjCIvarCallback = [&](ParsingFieldDeclarator &FD, Decl *&Dcl) {
assert(getObjCDeclContext() == interfaceDecl &&
"Ivar should have interfaceDecl as its decl context");
// Install the declarator into the interface decl.
@@ -2035,6 +2036,7 @@ void Parser::ParseObjCClassInstanceVariables(ObjCContainerDecl *interfaceDecl,
if (Field)
AllIvarDecls.push_back(Field);
FD.complete(Field);
+ Dcl = Field;
};
// Parse all the comma separated declarators.
diff --git a/clang/lib/Sema/SemaDeclAttr.cpp b/clang/lib/Sema/SemaDeclAttr.cpp
index 8bce04640e748e..42c144c3ce5573 100644
--- a/clang/lib/Sema/SemaDeclAttr.cpp
+++ b/clang/lib/Sema/SemaDeclAttr.cpp
@@ -8593,8 +8593,8 @@ static const RecordDecl *GetEnclosingNamedOrTopAnonRecord(const FieldDecl *FD) {
}
static bool
-CheckCountExpr(Sema &S, FieldDecl *FD, Expr *E,
- llvm::SmallVectorImpl<TypeCoupledDeclRefInfo> &Decls) {
+CheckCountExprOnField(Sema &S, FieldDecl *FD, Expr *E,
+ llvm::SmallVectorImpl<TypeCoupledDeclRefInfo> &Decls) {
if (FD->getParent()->isUnion()) {
S.Diag(FD->getBeginLoc(), diag::err_counted_by_attr_in_union)
<< FD->getSourceRange();
@@ -8610,7 +8610,8 @@ CheckCountExpr(Sema &S, FieldDecl *FD, Expr *E,
LangOptions::StrictFlexArraysLevelKind StrictFlexArraysLevel =
LangOptions::StrictFlexArraysLevelKind::IncompleteOnly;
- if (!Decl::isFlexibleArrayMemberLike(S.getASTContext(), FD, FD->getType(),
+ if (FD->getType()->isArrayType() &&
+ !Decl::isFlexibleArrayMemberLike(S.getASTContext(), FD, FD->getType(),
StrictFlexArraysLevel, true)) {
// The "counted_by" attribute must be on a flexible array member.
SourceRange SR = FD->getLocation();
@@ -8679,10 +8680,10 @@ static void handleCountedByAttrField(Sema &S, Decl *D, const ParsedAttr &AL) {
return;
llvm::SmallVector<TypeCoupledDeclRefInfo, 1> Decls;
- if (CheckCountExpr(S, FD, CountExpr, Decls))
+ if (CheckCountExprOnField(S, FD, CountExpr, Decls))
return;
- QualType CAT = S.BuildCountAttributedArrayType(FD->getType(), CountExpr);
+ QualType CAT = S.BuildCountAttributedArrayOrPointerType(FD->getType(), CountExpr);
FD->setType(CAT);
}
diff --git a/clang/lib/Sema/SemaType.cpp b/clang/lib/Sema/SemaType.cpp
index 8762744396f4dd..6cfac4f9ec6c3b 100644
--- a/clang/lib/Sema/SemaType.cpp
+++ b/clang/lib/Sema/SemaType.cpp
@@ -9786,9 +9786,9 @@ BuildTypeCoupledDecls(Expr *E,
Decls.push_back(TypeCoupledDeclRefInfo(CountDecl, /*IsDref*/ false));
}
-QualType Sema::BuildCountAttributedArrayType(QualType WrappedTy,
- Expr *CountExpr) {
- assert(WrappedTy->isIncompleteArrayType());
+QualType Sema::BuildCountAttributedArrayOrPointerType(QualType WrappedTy,
+ Expr *CountExpr) {
+ assert(WrappedTy->isIncompleteArrayType() || WrappedTy->isPointerType());
llvm::SmallVector<TypeCoupledDeclRefInfo, 1> Decls;
BuildTypeCoupledDecls(CountExpr, Decls);
diff --git a/clang/lib/Sema/TreeTransform.h b/clang/lib/Sema/TreeTransform.h
index a2568ad0f82cc2..97bf2e50fdc6cf 100644
--- a/clang/lib/Sema/TreeTransform.h
+++ b/clang/lib/Sema/TreeTransform.h
@@ -7308,7 +7308,7 @@ QualType TreeTransform<Derived>::TransformCountAttributedType(
if (getDerived().AlwaysRebuild() || InnerTy != OldTy->desugar() ||
OldCount != NewCount) {
// Currently, CountAttributedType can only wrap incomplete array types.
- Result = SemaRef.BuildCountAttributedArrayType(InnerTy, NewCount);
+ Result = SemaRef.BuildCountAttributedArrayOrPointerType(InnerTy, NewCount);
}
TLB.push<CountAttributedTypeLoc>(Result);
diff --git a/clang/test/AST/attr-counted-by-late-parsed.c b/clang/test/AST/attr-counted-by-late-parsed.c
new file mode 100644
index 00000000000000..ada479189bc5b2
--- /dev/null
+++ b/clang/test/AST/attr-counted-by-late-parsed.c
@@ -0,0 +1,29 @@
+// RUN: %clang_cc1 %s -ast-dump | FileCheck %s
+
+#define __counted_by(f) __attribute__((counted_by(f)))
+
+struct size_known;
+
+struct at_decl {
+ struct size_known *buf __counted_by(count);
+ int count;
+};
+// CHECK-LABEL: struct at_decl definition
+// CHECK-NEXT: |-FieldDecl {{.*}} buf 'struct size_known * __counted_by(count)':'struct size_known *'
+// CHECK-NEXT: `-FieldDecl {{.*}} referenced count 'int'
+
+struct at_decl_anon_count {
+ struct size_known *buf __counted_by(count);
+ struct {
+ int count;
+ };
+};
+
+// CHECK-LABEL: struct at_decl_anon_count definition
+// CHECK-NEXT: |-FieldDecl {{.*}} buf 'struct size_known * __counted_by(count)':'struct size_known *'
+// CHECK-NEXT: |-RecordDecl {{.*}} struct definition
+// CHECK-NEXT: | `-FieldDecl {{.*}} count 'int'
+// CHECK-NEXT: |-FieldDecl {{.*}} implicit 'struct at_decl_anon_count::(anonymous at {{.*}})'
+// CHECK-NEXT: `-IndirectFieldDecl {{.*}} implicit referenced count 'int'
+// CHECK-NEXT: |-Field {{.*}} '' 'struct at_decl_anon_count::(anonymous at {{.*}})'
+// CHECK-NEXT: `-Field {{.*}} 'count' 'int'
diff --git a/clang/test/Sema/attr-counted-by-late-parsed.c b/clang/test/Sema/attr-counted-by-late-parsed.c
new file mode 100644
index 00000000000000..0c471e3f1520a8
--- /dev/null
+++ b/clang/test/Sema/attr-counted-by-late-parsed.c
@@ -0,0 +1,54 @@
+// RUN: %clang_cc1 -fsyntax-only -verify %s
+
+#define __counted_by(f) __attribute__((counted_by(f)))
+
+struct size_unknown;
+
+struct at_pointer {
+ int count;
+ struct size_unknown *__counted_by(count) buf; // expected-error{{'counted_by' cannot be applied to an sized type}}
+};
+
+struct size_known { int dummy; };
+
+struct at_nested_pointer {
+ // FIXME
+ struct size_known *__counted_by(count) *buf; // expected-error{{use of undeclared identifier 'count'}}
+ int count;
+};
+
+struct at_decl {
+ struct size_known *buf __counted_by(count);
+ int count;
+};
+
+struct at_pointer_anon_buf {
+ struct {
+ // FIXME
+ struct size_known *__counted_by(count) buf; // expected-error{{use of undeclared identifier 'count'}}
+ };
+ int count;
+};
+
+struct at_decl_anon_buf {
+ struct {
+ // FIXME
+ struct size_known *buf __counted_by(count); // expected-error{{use of undeclared identifier 'count'}}
+ };
+ int count;
+};
+
+struct at_pointer_anon_count {
+ // FIXME
+ struct size_known *__counted_by(count) buf; // expected-error{{use of undeclared identifier 'count'}}
+ struct {
+ int count;
+ };
+};
+
+struct at_decl_anon_count {
+ struct size_known *buf __counted_by(count);
+ struct {
+ int count;
+ };
+};
\ No newline at end of file
diff --git a/clang/test/Sema/attr-counted-by.c b/clang/test/Sema/attr-counted-by.c
index d5d4ebf5573922..b0496af2fb4d8c 100644
--- a/clang/test/Sema/attr-counted-by.c
+++ b/clang/test/Sema/attr-counted-by.c
@@ -95,7 +95,7 @@ struct array_of_ints_count {
struct not_a_fam {
int count;
- struct bar *non_fam __counted_by(count); // expected-error {{'counted_by' only applies to C99 flexible array members}}
+ struct bar *non_fam __counted_by(count);
};
struct not_a_c99_fam {
>From 91538acab76903608c345f9b93908b9cbd2f51b1 Mon Sep 17 00:00:00 2001
From: Dan Liew <dan at su-root.co.uk>
Date: Wed, 10 Apr 2024 17:18:03 -0700
Subject: [PATCH 2/3] [WIP] Add experimental late parsing mode
---
clang/include/clang/Basic/Attr.td | 48 +++---
clang/include/clang/Basic/LangOptions.def | 1 +
clang/include/clang/Driver/Options.td | 7 +
clang/include/clang/Parse/Parser.h | 4 +
clang/lib/Driver/ToolChains/Clang.cpp | 3 +
clang/lib/Parse/ParseDecl.cpp | 19 ++-
.../experimental-late-parse-attributes.c | 12 ++
clang/utils/TableGen/ClangAttrEmitter.cpp | 137 ++++++++++++++++--
8 files changed, 193 insertions(+), 38 deletions(-)
create mode 100644 clang/test/Driver/experimental-late-parse-attributes.c
diff --git a/clang/include/clang/Basic/Attr.td b/clang/include/clang/Basic/Attr.td
index d6caa28938edef..a0513bf8e5634d 100644
--- a/clang/include/clang/Basic/Attr.td
+++ b/clang/include/clang/Basic/Attr.td
@@ -595,6 +595,16 @@ class AttrSubjectMatcherAggregateRule<AttrSubject subject> {
def SubjectMatcherForNamed : AttrSubjectMatcherAggregateRule<Named>;
+// Late Attribute parsing mode enum
+class LateAttrParseKind <int val> {
+ int Kind = val;
+}
+def LateAttrParsingNever : LateAttrParseKind<0>; // Never late parsed
+def LateAttrParsingAlways: LateAttrParseKind<1>; // Always late parsed
+// Late parsed if `-fexperimental-late-parse-attributes` is on and parsed
+// normally if off.
+def LateAttrParsingExperimentalOnly : LateAttrParseKind<2>;
+
class Attr {
// Specifies that when printed, this attribute is meaningful on the
// 'left side' of the declaration.
@@ -612,8 +622,8 @@ class Attr {
list<Accessor> Accessors = [];
// Specify targets for spellings.
list<TargetSpecificSpelling> TargetSpecificSpellings = [];
- // Set to true for attributes with arguments which require delayed parsing.
- bit LateParsed = 0;
+ // Specifies the late parsing kind.
+ LateAttrParseKind LateParsed = LateAttrParsingNever;
// Set to false to prevent an attribute from being propagated from a template
// to the instantiation.
bit Clone = 1;
@@ -2198,7 +2208,7 @@ def CountedBy : DeclOrTypeAttr {
let Spellings = [Clang<"counted_by">];
let Subjects = SubjectList<[Field], ErrorDiag>;
let Args = [ExprArgument<"Count">, IntArgument<"NestedLevel", 1>];
- let LateParsed = 1;
+ let LateParsed = LateAttrParsingExperimentalOnly;
let ParseArgumentsAsUnevaluated = 1;
let Documentation = [CountedByDocs];
let LangOpts = [COnly];
@@ -3186,7 +3196,7 @@ def DiagnoseIf : InheritableAttr {
BoolArgument<"ArgDependent", 0, /*fake*/ 1>,
DeclArgument<Named, "Parent", 0, /*fake*/ 1>];
let InheritEvenIfAlreadyPresent = 1;
- let LateParsed = 1;
+ let LateParsed = LateAttrParsingAlways;
let AdditionalMembers = [{
bool isError() const { return diagnosticType == DT_Error; }
bool isWarning() const { return diagnosticType == DT_Warning; }
@@ -3485,7 +3495,7 @@ def AssertCapability : InheritableAttr {
let Spellings = [Clang<"assert_capability", 0>,
Clang<"assert_shared_capability", 0>];
let Subjects = SubjectList<[Function]>;
- let LateParsed = 1;
+ let LateParsed = LateAttrParsingAlways;
let TemplateDependent = 1;
let ParseArgumentsAsUnevaluated = 1;
let InheritEvenIfAlreadyPresent = 1;
@@ -3501,7 +3511,7 @@ def AcquireCapability : InheritableAttr {
GNU<"exclusive_lock_function">,
GNU<"shared_lock_function">];
let Subjects = SubjectList<[Function]>;
- let LateParsed = 1;
+ let LateParsed = LateAttrParsingAlways;
let TemplateDependent = 1;
let ParseArgumentsAsUnevaluated = 1;
let InheritEvenIfAlreadyPresent = 1;
@@ -3517,7 +3527,7 @@ def TryAcquireCapability : InheritableAttr {
Clang<"try_acquire_shared_capability", 0>];
let Subjects = SubjectList<[Function],
ErrorDiag>;
- let LateParsed = 1;
+ let LateParsed = LateAttrParsingAlways;
let TemplateDependent = 1;
let ParseArgumentsAsUnevaluated = 1;
let InheritEvenIfAlreadyPresent = 1;
@@ -3533,7 +3543,7 @@ def ReleaseCapability : InheritableAttr {
Clang<"release_generic_capability", 0>,
Clang<"unlock_function", 0>];
let Subjects = SubjectList<[Function]>;
- let LateParsed = 1;
+ let LateParsed = LateAttrParsingAlways;
let TemplateDependent = 1;
let ParseArgumentsAsUnevaluated = 1;
let InheritEvenIfAlreadyPresent = 1;
@@ -3552,7 +3562,7 @@ def RequiresCapability : InheritableAttr {
Clang<"requires_shared_capability", 0>,
Clang<"shared_locks_required", 0>];
let Args = [VariadicExprArgument<"Args">];
- let LateParsed = 1;
+ let LateParsed = LateAttrParsingAlways;
let TemplateDependent = 1;
let ParseArgumentsAsUnevaluated = 1;
let InheritEvenIfAlreadyPresent = 1;
@@ -3572,7 +3582,7 @@ def NoThreadSafetyAnalysis : InheritableAttr {
def GuardedBy : InheritableAttr {
let Spellings = [GNU<"guarded_by">];
let Args = [ExprArgument<"Arg">];
- let LateParsed = 1;
+ let LateParsed = LateAttrParsingAlways;
let TemplateDependent = 1;
let ParseArgumentsAsUnevaluated = 1;
let InheritEvenIfAlreadyPresent = 1;
@@ -3583,7 +3593,7 @@ def GuardedBy : InheritableAttr {
def PtGuardedBy : InheritableAttr {
let Spellings = [GNU<"pt_guarded_by">];
let Args = [ExprArgument<"Arg">];
- let LateParsed = 1;
+ let LateParsed = LateAttrParsingAlways;
let TemplateDependent = 1;
let ParseArgumentsAsUnevaluated = 1;
let InheritEvenIfAlreadyPresent = 1;
@@ -3594,7 +3604,7 @@ def PtGuardedBy : InheritableAttr {
def AcquiredAfter : InheritableAttr {
let Spellings = [GNU<"acquired_after">];
let Args = [VariadicExprArgument<"Args">];
- let LateParsed = 1;
+ let LateParsed = LateAttrParsingAlways;
let TemplateDependent = 1;
let ParseArgumentsAsUnevaluated = 1;
let InheritEvenIfAlreadyPresent = 1;
@@ -3605,7 +3615,7 @@ def AcquiredAfter : InheritableAttr {
def AcquiredBefore : InheritableAttr {
let Spellings = [GNU<"acquired_before">];
let Args = [VariadicExprArgument<"Args">];
- let LateParsed = 1;
+ let LateParsed = LateAttrParsingAlways;
let TemplateDependent = 1;
let ParseArgumentsAsUnevaluated = 1;
let InheritEvenIfAlreadyPresent = 1;
@@ -3616,7 +3626,7 @@ def AcquiredBefore : InheritableAttr {
def AssertExclusiveLock : InheritableAttr {
let Spellings = [GNU<"assert_exclusive_lock">];
let Args = [VariadicExprArgument<"Args">];
- let LateParsed = 1;
+ let LateParsed = LateAttrParsingAlways;
let TemplateDependent = 1;
let ParseArgumentsAsUnevaluated = 1;
let InheritEvenIfAlreadyPresent = 1;
@@ -3627,7 +3637,7 @@ def AssertExclusiveLock : InheritableAttr {
def AssertSharedLock : InheritableAttr {
let Spellings = [GNU<"assert_shared_lock">];
let Args = [VariadicExprArgument<"Args">];
- let LateParsed = 1;
+ let LateParsed = LateAttrParsingAlways;
let TemplateDependent = 1;
let ParseArgumentsAsUnevaluated = 1;
let InheritEvenIfAlreadyPresent = 1;
@@ -3640,7 +3650,7 @@ def AssertSharedLock : InheritableAttr {
def ExclusiveTrylockFunction : InheritableAttr {
let Spellings = [GNU<"exclusive_trylock_function">];
let Args = [ExprArgument<"SuccessValue">, VariadicExprArgument<"Args">];
- let LateParsed = 1;
+ let LateParsed = LateAttrParsingAlways;
let TemplateDependent = 1;
let ParseArgumentsAsUnevaluated = 1;
let InheritEvenIfAlreadyPresent = 1;
@@ -3653,7 +3663,7 @@ def ExclusiveTrylockFunction : InheritableAttr {
def SharedTrylockFunction : InheritableAttr {
let Spellings = [GNU<"shared_trylock_function">];
let Args = [ExprArgument<"SuccessValue">, VariadicExprArgument<"Args">];
- let LateParsed = 1;
+ let LateParsed = LateAttrParsingAlways;
let TemplateDependent = 1;
let ParseArgumentsAsUnevaluated = 1;
let InheritEvenIfAlreadyPresent = 1;
@@ -3664,7 +3674,7 @@ def SharedTrylockFunction : InheritableAttr {
def LockReturned : InheritableAttr {
let Spellings = [GNU<"lock_returned">];
let Args = [ExprArgument<"Arg">];
- let LateParsed = 1;
+ let LateParsed = LateAttrParsingAlways;
let TemplateDependent = 1;
let ParseArgumentsAsUnevaluated = 1;
let Subjects = SubjectList<[Function]>;
@@ -3674,7 +3684,7 @@ def LockReturned : InheritableAttr {
def LocksExcluded : InheritableAttr {
let Spellings = [GNU<"locks_excluded">];
let Args = [VariadicExprArgument<"Args">];
- let LateParsed = 1;
+ let LateParsed = LateAttrParsingAlways;
let TemplateDependent = 1;
let ParseArgumentsAsUnevaluated = 1;
let InheritEvenIfAlreadyPresent = 1;
diff --git a/clang/include/clang/Basic/LangOptions.def b/clang/include/clang/Basic/LangOptions.def
index 8ef6700ecdc78e..ed483fe8df131a 100644
--- a/clang/include/clang/Basic/LangOptions.def
+++ b/clang/include/clang/Basic/LangOptions.def
@@ -164,6 +164,7 @@ LANGOPT(ExperimentalLibrary, 1, 0, "enable unstable and experimental library fea
LANGOPT(PointerAuthIntrinsics, 1, 0, "pointer authentication intrinsics")
LANGOPT(DoubleSquareBracketAttributes, 1, 0, "'[[]]' attributes extension for all language standard modes")
+LANGOPT(ExperimentalLateParseAttributes, 1, 0, "enable experimental late parsing of attributes")
COMPATIBLE_LANGOPT(RecoveryAST, 1, 1, "Preserve expressions in AST when encountering errors")
COMPATIBLE_LANGOPT(RecoveryASTType, 1, 1, "Preserve the type in recovery expressions")
diff --git a/clang/include/clang/Driver/Options.td b/clang/include/clang/Driver/Options.td
index c3e90a70925b78..a94e4f6f42d1c1 100644
--- a/clang/include/clang/Driver/Options.td
+++ b/clang/include/clang/Driver/Options.td
@@ -1599,6 +1599,13 @@ defm double_square_bracket_attributes : BoolFOption<"double-square-bracket-attri
LangOpts<"DoubleSquareBracketAttributes">, DefaultTrue, PosFlag<SetTrue>,
NegFlag<SetFalse>>;
+defm experimental_late_parse_attributes : BoolFOption<"experimental-late-parse-attributes",
+ LangOpts<"ExperimentalLateParseAttributes">, DefaultFalse,
+ PosFlag<SetTrue, [], [ClangOption], "Enable">,
+ NegFlag<SetFalse, [], [ClangOption], "Disable">,
+ BothFlags<[], [ClangOption, CC1Option],
+ " experimental late parsing of attributes">>;
+
defm autolink : BoolFOption<"autolink",
CodeGenOpts<"Autolink">, DefaultTrue,
NegFlag<SetFalse, [], [ClangOption, CC1Option],
diff --git a/clang/include/clang/Parse/Parser.h b/clang/include/clang/Parse/Parser.h
index 41fc5d5dc98a6b..d88b8fbe2e526d 100644
--- a/clang/include/clang/Parse/Parser.h
+++ b/clang/include/clang/Parse/Parser.h
@@ -2968,6 +2968,10 @@ class Parser : public CodeCompletionHandler {
SourceLocation AttrNameLoc,
SourceLocation *EndLoc);
+ /// isAttributeLateParsed - Return true if the attribute has arguments that
+ /// require late parsing.
+ bool isAttributeLateParsed(const IdentifierInfo &II);
+
IdentifierInfo *TryParseCXX11AttributeIdentifier(
SourceLocation &Loc,
Sema::AttributeCompletion Completion = Sema::AttributeCompletion::None,
diff --git a/clang/lib/Driver/ToolChains/Clang.cpp b/clang/lib/Driver/ToolChains/Clang.cpp
index b7ec7e0a60977b..4676a176ed65fb 100644
--- a/clang/lib/Driver/ToolChains/Clang.cpp
+++ b/clang/lib/Driver/ToolChains/Clang.cpp
@@ -7479,6 +7479,9 @@ void Clang::ConstructJob(Compilation &C, const JobAction &JA,
Args.addOptInFlag(CmdArgs, options::OPT_fsafe_buffer_usage_suggestions,
options::OPT_fno_safe_buffer_usage_suggestions);
+ Args.addOptInFlag(CmdArgs, options::OPT_fexperimental_late_parse_attributes,
+ options::OPT_fno_experimental_late_parse_attributes);
+
// Setup statistics file output.
SmallString<128> StatsFile = getStatsFileName(Args, Output, Input, D);
if (!StatsFile.empty()) {
diff --git a/clang/lib/Parse/ParseDecl.cpp b/clang/lib/Parse/ParseDecl.cpp
index 1c2e6d447cf5d5..d61276be4627a4 100644
--- a/clang/lib/Parse/ParseDecl.cpp
+++ b/clang/lib/Parse/ParseDecl.cpp
@@ -91,11 +91,24 @@ static StringRef normalizeAttrName(StringRef Name) {
/// isAttributeLateParsed - Return true if the attribute has arguments that
/// require late parsing.
-static bool isAttributeLateParsed(const IdentifierInfo &II) {
+bool Parser::isAttributeLateParsed(const IdentifierInfo &II) {
+ // Some attributes might only be late parsed when in the experimental
+ // language mode.
+ if (getLangOpts().ExperimentalLateParseAttributes) {
+#define CLANG_ATTR_LATE_PARSED_EXPERIMENTAL_LIST
+ bool IsExperimentalLateParseAttr =
+ llvm::StringSwitch<bool>(normalizeAttrName(II.getName()))
+#include "clang/Parse/AttrParserStringSwitches.inc"
+ .Default(false);
+#undef CLANG_ATTR_LATE_PARSED_EXPERIMENTAL_LIST
+ if (IsExperimentalLateParseAttr)
+ return true;
+ }
+
#define CLANG_ATTR_LATE_PARSED_LIST
- return llvm::StringSwitch<bool>(normalizeAttrName(II.getName()))
+ return llvm::StringSwitch<bool>(normalizeAttrName(II.getName()))
#include "clang/Parse/AttrParserStringSwitches.inc"
- .Default(false);
+ .Default(false);
#undef CLANG_ATTR_LATE_PARSED_LIST
}
diff --git a/clang/test/Driver/experimental-late-parse-attributes.c b/clang/test/Driver/experimental-late-parse-attributes.c
new file mode 100644
index 00000000000000..6b54b898afa749
--- /dev/null
+++ b/clang/test/Driver/experimental-late-parse-attributes.c
@@ -0,0 +1,12 @@
+// RUN: %clang %s -c -fexperimental-late-parse-attributes 2>&1 -### | FileCheck %s -check-prefix=CHECK-ON
+// RUN: %clang %s -c -fno-experimental-late-parse-attributes -fexperimental-late-parse-attributes 2>&1 -### | FileCheck %s -check-prefix=CHECK-ON
+
+// CHECK-ON: -cc1
+// CHECK-ON: -fexperimental-late-parse-attributes
+
+// RUN: %clang %s -c 2>&1 -### | FileCheck %s -check-prefix=CHECK-OFF
+// RUN: %clang %s -c -fno-experimental-late-parse-attributes 2>&1 -### | FileCheck %s -check-prefix=CHECK-OFF
+// RUN: %clang %s -c -fexperimental-late-parse-attributes -fno-experimental-late-parse-attributes 2>&1 -### | FileCheck %s -check-prefix=CHECK-OFF
+
+// CHECK-OFF: -cc1
+// CHECK-OFF-NOT: -fexperimental-late-parse-attributes
diff --git a/clang/utils/TableGen/ClangAttrEmitter.cpp b/clang/utils/TableGen/ClangAttrEmitter.cpp
index eb5c34d15693d7..c52394285eefab 100644
--- a/clang/utils/TableGen/ClangAttrEmitter.cpp
+++ b/clang/utils/TableGen/ClangAttrEmitter.cpp
@@ -1822,28 +1822,106 @@ void WriteSemanticSpellingSwitch(const std::string &VarName,
OS << " }\n";
}
+enum class LateAttrParseKind {
+ LateAttrParsingNever = 0,
+ LateAttrParsingAlways = 1,
+ LateAttrParsingExperimentalOnly = 2
+};
+
+static LateAttrParseKind getLateAttrParseKind(const Record *Attr) {
+ // This function basically does
+ // `Attr->getValueAsDef("LateParsed")->getValueAsInt("Mode")` but does
+ // a bunch of sanity checking in an asserts build to ensure that
+ // `LateAttrParseMode` in `Attr.td` is in sync with the `LateAttrParseKind`
+ // enum in this source file.
+
+ const char *LateParsedStr = "LateParsed";
+ auto *LAPK = Attr->getValueAsDef(LateParsedStr);
+
+ // Typecheck the `LateParsed` field.
+ SmallVector<Record *, 1> SuperClasses;
+ LAPK->getDirectSuperClasses(SuperClasses);
+ if (SuperClasses.size() != 1) {
+ PrintFatalError(Attr, "Field `" + llvm::Twine(LateParsedStr) +
+ "`should only have one super class");
+ }
+ const char *LateAttrParseKindStr = "LateAttrParseKind";
+ if (SuperClasses[0]->getName().compare(LateAttrParseKindStr) != 0) {
+ PrintFatalError(Attr, "Field `" + llvm::Twine(LateParsedStr) +
+ "`should only have type `" +
+ llvm::Twine(LateAttrParseKindStr) +
+ "` but found type `" +
+ SuperClasses[0]->getName() + "`");
+ }
+
+ // Get Kind and verify the enum name matches the name in `Attr.td`.
+ const char *KindFieldStr = "Kind";
+ unsigned Kind = LAPK->getValueAsInt(KindFieldStr);
+ switch (LateAttrParseKind(Kind)) {
+#define CASE(X) \
+ case LateAttrParseKind::X: \
+ if (LAPK->getName().compare(#X) != 0) { \
+ PrintFatalError(Attr, \
+ "Field `" + llvm::Twine(LateParsedStr) + "` set to `" + \
+ LAPK->getName() + \
+ "` but this converts to `LateAttrParseKind::" + \
+ llvm::Twine(#X) + "`"); \
+ } \
+ return LateAttrParseKind::X;
+
+ CASE(LateAttrParsingNever)
+ CASE(LateAttrParsingAlways)
+ CASE(LateAttrParsingExperimentalOnly)
+#undef CASE
+ }
+
+ // The Kind value is completely invalid
+ auto KindValueStr = llvm::utostr(Kind);
+ PrintFatalError(Attr, "Field `" + llvm::Twine(LateParsedStr) + "` set to `" +
+ LAPK->getName() + "` has unexpected `" +
+ llvm::Twine(KindFieldStr) + "` value of " +
+ KindValueStr);
+}
+
// Emits the LateParsed property for attributes.
-static void emitClangAttrLateParsedList(RecordKeeper &Records, raw_ostream &OS) {
- OS << "#if defined(CLANG_ATTR_LATE_PARSED_LIST)\n";
- std::vector<Record*> Attrs = Records.getAllDerivedDefinitions("Attr");
+static void emitClangAttrLateParsedListImpl(RecordKeeper &Records,
+ raw_ostream &OS,
+ LateAttrParseKind LateParseMode) {
+ std::vector<Record *> Attrs = Records.getAllDerivedDefinitions("Attr");
for (const auto *Attr : Attrs) {
- bool LateParsed = Attr->getValueAsBit("LateParsed");
+ auto LateParsed = getLateAttrParseKind(Attr);
- if (LateParsed) {
- std::vector<FlattenedSpelling> Spellings = GetFlattenedSpellings(*Attr);
+ if (LateParsed != LateParseMode)
+ continue;
- // FIXME: Handle non-GNU attributes
- for (const auto &I : Spellings) {
- if (I.variety() != "GNU")
- continue;
- OS << ".Case(\"" << I.name() << "\", " << LateParsed << ")\n";
- }
+ std::vector<FlattenedSpelling> Spellings = GetFlattenedSpellings(*Attr);
+
+ // FIXME: Handle non-GNU attributes
+ for (const auto &I : Spellings) {
+ if (I.variety() != "GNU")
+ continue;
+ OS << ".Case(\"" << I.name() << "\", 1)\n";
}
}
+}
+
+static void emitClangAttrLateParsedList(RecordKeeper &Records,
+ raw_ostream &OS) {
+ OS << "#if defined(CLANG_ATTR_LATE_PARSED_LIST)\n";
+ emitClangAttrLateParsedListImpl(Records, OS,
+ LateAttrParseKind::LateAttrParsingAlways);
OS << "#endif // CLANG_ATTR_LATE_PARSED_LIST\n\n";
}
+static void emitClangAttrLateParsedExperimentalList(RecordKeeper &Records,
+ raw_ostream &OS) {
+ OS << "#if defined(CLANG_ATTR_LATE_PARSED_EXPERIMENTAL_LIST)\n";
+ emitClangAttrLateParsedListImpl(
+ Records, OS, LateAttrParseKind::LateAttrParsingExperimentalOnly);
+ OS << "#endif // CLANG_ATTR_LATE_PARSED_EXPERIMENTAL_LIST\n\n";
+}
+
static bool hasGNUorCXX11Spelling(const Record &Attribute) {
std::vector<FlattenedSpelling> Spellings = GetFlattenedSpellings(Attribute);
for (const auto &I : Spellings) {
@@ -2101,9 +2179,20 @@ bool PragmaClangAttributeSupport::isAttributedSupported(
return SpecifiedResult;
// Opt-out rules:
- // An attribute requires delayed parsing (LateParsed is on)
- if (Attribute.getValueAsBit("LateParsed"))
+
+ // An attribute requires delayed parsing (LateParsed is on).
+ switch (getLateAttrParseKind(&Attribute)) {
+ case LateAttrParseKind::LateAttrParsingNever:
+ break;
+ case LateAttrParseKind::LateAttrParsingAlways:
+ return false;
+ case LateAttrParseKind::LateAttrParsingExperimentalOnly:
+ // FIXME: This is only late parsed when
+ // `-fexperimental-late-parse-attributes` is on. If that option is off
+ // then we shouldn't return here.
return false;
+ }
+
// An attribute has no GNU/CXX11 spelling
if (!hasGNUorCXX11Spelling(Attribute))
return false;
@@ -2885,8 +2974,23 @@ static void emitAttributes(RecordKeeper &Records, raw_ostream &OS,
return;
}
OS << "\n : " << SuperName << "(Ctx, CommonInfo, ";
- OS << "attr::" << R.getName() << ", "
- << (R.getValueAsBit("LateParsed") ? "true" : "false");
+ OS << "attr::" << R.getName() << ", ";
+
+ // Handle different late parsing modes.
+ switch (getLateAttrParseKind(&R)) {
+ case LateAttrParseKind::LateAttrParsingNever:
+ OS << '0';
+ break;
+ case LateAttrParseKind::LateAttrParsingAlways:
+ OS << '1';
+ break;
+ case LateAttrParseKind::LateAttrParsingExperimentalOnly:
+ // Emit code that determines if Late Parsing is enabled based on
+ // the LangOpts.
+ OS << "Ctx.getLangOpts().ExperimentalLateParseAttributes";
+ break;
+ }
+
if (Inheritable) {
OS << ", "
<< (R.getValueAsBit("InheritEvenIfAlreadyPresent") ? "true"
@@ -4874,6 +4978,7 @@ void EmitClangAttrParserStringSwitches(RecordKeeper &Records, raw_ostream &OS) {
emitClangAttrAcceptsExprPack(Records, OS);
emitClangAttrTypeArgList(Records, OS);
emitClangAttrLateParsedList(Records, OS);
+ emitClangAttrLateParsedExperimentalList(Records, OS);
}
void EmitClangAttrSubjectMatchRulesParserStringSwitches(RecordKeeper &Records,
>From 560b9bb20f5079f130a56a0df24672619d77f541 Mon Sep 17 00:00:00 2001
From: Dan Liew <dan at su-root.co.uk>
Date: Fri, 12 Apr 2024 16:26:17 -0700
Subject: [PATCH 3/3] [WIP] Test case fix up
---
clang/test/AST/attr-counted-by-late-parsed.c | 2 +-
.../Sema/attr-counted-by-late-parsed-off.c | 27 +++++++++++++++++++
clang/test/Sema/attr-counted-by-late-parsed.c | 4 +--
3 files changed, 30 insertions(+), 3 deletions(-)
create mode 100644 clang/test/Sema/attr-counted-by-late-parsed-off.c
diff --git a/clang/test/AST/attr-counted-by-late-parsed.c b/clang/test/AST/attr-counted-by-late-parsed.c
index ada479189bc5b2..1118de7b47abed 100644
--- a/clang/test/AST/attr-counted-by-late-parsed.c
+++ b/clang/test/AST/attr-counted-by-late-parsed.c
@@ -1,4 +1,4 @@
-// RUN: %clang_cc1 %s -ast-dump | FileCheck %s
+// RUN: %clang_cc1 -fexperimental-late-parse-attributes %s -ast-dump | FileCheck %s
#define __counted_by(f) __attribute__((counted_by(f)))
diff --git a/clang/test/Sema/attr-counted-by-late-parsed-off.c b/clang/test/Sema/attr-counted-by-late-parsed-off.c
new file mode 100644
index 00000000000000..4f64d1654e4b33
--- /dev/null
+++ b/clang/test/Sema/attr-counted-by-late-parsed-off.c
@@ -0,0 +1,27 @@
+// RUN: %clang_cc1 -DNEEDS_LATE_PARSING -fno-experimental-late-parse-attributes -fsyntax-only -verify %s
+// Default behavior is to not allow late parsing counted_by
+// RUN: %clang_cc1 -DNEEDS_LATE_PARSING -fsyntax-only -verify %s
+
+// RUN: %clang_cc1 -UNEEDS_LATE_PARSING -fno-experimental-late-parse-attributes -fsyntax-only -verify=ok %s
+// RUN: %clang_cc1 -UNEEDS_LATE_PARSING -fsyntax-only -verify=ok %s
+
+#define __counted_by(f) __attribute__((counted_by(f)))
+
+struct size_known { int dummy; };
+
+#ifdef NEEDS_LATE_PARSING
+struct at_decl {
+ // expected-error at +1{{use of undeclared identifier 'count'}}
+ struct size_known *buf __counted_by(count);
+ int count;
+};
+
+#else
+
+// ok-no-diagnostics
+struct at_decl {
+ int count;
+ struct size_known *buf __counted_by(count);
+};
+
+#endif
diff --git a/clang/test/Sema/attr-counted-by-late-parsed.c b/clang/test/Sema/attr-counted-by-late-parsed.c
index 0c471e3f1520a8..4a091d36b6d7b4 100644
--- a/clang/test/Sema/attr-counted-by-late-parsed.c
+++ b/clang/test/Sema/attr-counted-by-late-parsed.c
@@ -1,4 +1,4 @@
-// RUN: %clang_cc1 -fsyntax-only -verify %s
+// RUN: %clang_cc1 -fexperimental-late-parse-attributes -fsyntax-only -verify %s
#define __counted_by(f) __attribute__((counted_by(f)))
@@ -51,4 +51,4 @@ struct at_decl_anon_count {
struct {
int count;
};
-};
\ No newline at end of file
+};
More information about the cfe-commits
mailing list