Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions clang/include/clang/Basic/DiagnosticSemaKinds.td
Original file line number Diff line number Diff line change
Expand Up @@ -7012,6 +7012,9 @@ def err_builtin_counted_by_ref_invalid_use : Error<
"value returned by '__builtin_counted_by_ref' cannot be used in "
"%select{an array subscript|a binary}0 expression">;

def err_counted_by_on_nested_pointer : Error<
"'%select{counted_by|sized_by|counted_by_or_null|sized_by_or_null}0' attribute on nested pointer type is not allowed">;

let CategoryName = "ARC Semantic Issue" in {

// ARC-mode diagnostics.
Expand Down
33 changes: 21 additions & 12 deletions clang/include/clang/Parse/Parser.h
Original file line number Diff line number Diff line change
Expand Up @@ -1161,10 +1161,13 @@ class Parser : public CodeCompletionHandler {
IdentifierInfo *MacroII = nullptr;
SourceLocation AttrNameLoc;
SmallVector<Decl *, 2> Decls;
unsigned NestedTypeLevel;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you explain what this means? This is awkwardly worded and isn't clear at all what it should represent.


explicit LateParsedAttribute(Parser *P, IdentifierInfo &Name,
SourceLocation Loc)
: Self(P), AttrName(Name), AttrNameLoc(Loc) {}
SourceLocation Loc,
unsigned NestedTypeLevel = 0)
: Self(P), AttrName(Name), AttrNameLoc(Loc),
NestedTypeLevel(NestedTypeLevel) {}

void ParseLexedAttributes() override;

Expand Down Expand Up @@ -1889,10 +1892,12 @@ class Parser : public CodeCompletionHandler {
DeclSpec &DS, AccessSpecifier AS, DeclSpecContext DSContext,
LateParsedAttrList *LateAttrs = nullptr);

void ParseSpecifierQualifierList(
DeclSpec &DS, AccessSpecifier AS = AS_none,
DeclSpecContext DSC = DeclSpecContext::DSC_normal) {
ParseSpecifierQualifierList(DS, getImplicitTypenameContext(DSC), AS, DSC);
void
ParseSpecifierQualifierList(DeclSpec &DS, AccessSpecifier AS = AS_none,
DeclSpecContext DSC = DeclSpecContext::DSC_normal,
LateParsedAttrList *LateAttrs = nullptr) {
ParseSpecifierQualifierList(DS, getImplicitTypenameContext(DSC), AS, DSC,
LateAttrs);
}

/// ParseSpecifierQualifierList
Expand All @@ -1903,10 +1908,12 @@ class Parser : public CodeCompletionHandler {
/// [GNU] attributes specifier-qualifier-list[opt]
/// \endverbatim
///
void ParseSpecifierQualifierList(
DeclSpec &DS, ImplicitTypenameContext AllowImplicitTypename,
AccessSpecifier AS = AS_none,
DeclSpecContext DSC = DeclSpecContext::DSC_normal);
void
ParseSpecifierQualifierList(DeclSpec &DS,
ImplicitTypenameContext AllowImplicitTypename,
AccessSpecifier AS = AS_none,
DeclSpecContext DSC = DeclSpecContext::DSC_normal,
LateParsedAttrList *LateAttrs = nullptr);

/// ParseEnumSpecifier
/// \verbatim
Expand Down Expand Up @@ -2444,7 +2451,8 @@ class Parser : public CodeCompletionHandler {
SourceLocation ScopeLoc,
ParsedAttr::Form Form);

void DistributeCLateParsedAttrs(Decl *Dcl, LateParsedAttrList *LateAttrs);
void DistributeCLateParsedAttrs(Declarator &D, Decl *Dcl,
LateParsedAttrList *LateAttrs);

/// Bounds attributes (e.g., counted_by):
/// \verbatim
Expand Down Expand Up @@ -2610,7 +2618,8 @@ class Parser : public CodeCompletionHandler {
void ParseTypeQualifierListOpt(
DeclSpec &DS, unsigned AttrReqs = AR_AllAttributesParsed,
bool AtomicOrPtrauthAllowed = true, bool IdentifierRequired = false,
llvm::function_ref<void()> CodeCompletionHandler = {});
llvm::function_ref<void()> CodeCompletionHandler = {},
LateParsedAttrList *LateAttrs = nullptr);

/// ParseDirectDeclarator
/// \verbatim
Expand Down
15 changes: 13 additions & 2 deletions clang/include/clang/Sema/DeclSpec.h
Original file line number Diff line number Diff line change
Expand Up @@ -1238,6 +1238,13 @@ struct DeclaratorChunk {

ParsedAttributesView AttrList;

/// Stores pointers to `Parser::LateParsedAttribute`. We use `void*` here
/// because `LateParsedAttribute` is a nested struct of `class Parser` and
/// cannot be forward-declared.
using LateAttrOpaquePtr = void *;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

THIS feels like a code smell, and I'm pretty uncomfortable by what is happening here.

I'm hopeful that @AaronBallman can come along and take a look at this.

Copy link
Contributor Author

@rapidsna rapidsna Nov 12, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We discussed other alternatives here: #166491 (comment)

I couldn't include Parser.h because of circular dependencies. I also looked at a few other options:

  • Duplicating the type in DeclSpec.h — In downstream, we created another struct called LateParsedAttrInfo in DeclSpec.h, but it's essentially a duplicate of Parser::LateParsedAttribute, so I don't think that's the right approach.
  • Forward declaring Parser::LateParsedAttribute — Can't do it since it's a nested member struct.
  • Hoisting LateParsedAttribute out of class Parser — It inherits from Parser::LateParsedDeclaration, which is inherited by multiple other member structs and referenced all over the codebase. That would require too invasive changes.

So using an opaque type seemed like a cleanest solution, but I'm open to other suggestions.

using LateAttrListTy = SmallVector<LateAttrOpaquePtr, 1>;
LateAttrListTy LateAttrList;

struct PointerTypeInfo {
/// The type qualifiers: const/volatile/restrict/unaligned/atomic.
LLVM_PREFERRED_TYPE(DeclSpec::TQ)
Expand Down Expand Up @@ -2324,14 +2331,18 @@ class Declarator {
/// EndLoc, which should be the last token of the chunk.
/// This function takes attrs by R-Value reference because it takes ownership
/// of those attributes from the parameter.
void AddTypeInfo(const DeclaratorChunk &TI, ParsedAttributes &&attrs,
SourceLocation EndLoc) {
void
AddTypeInfo(const DeclaratorChunk &TI, ParsedAttributes &&attrs,
SourceLocation EndLoc,
ArrayRef<DeclaratorChunk::LateAttrOpaquePtr> LateAttrs = {}) {
DeclTypeInfo.push_back(TI);
DeclTypeInfo.back().getAttrs().prepend(attrs.begin(), attrs.end());
getAttributePool().takeAllFrom(attrs.getPool());

if (!EndLoc.isInvalid())
SetRangeEnd(EndLoc);

DeclTypeInfo.back().LateAttrList.assign(LateAttrs);
}

/// AddTypeInfo - Add a chunk to this declarator. Also extend the range to
Expand Down
11 changes: 8 additions & 3 deletions clang/include/clang/Sema/Sema.h
Original file line number Diff line number Diff line change
Expand Up @@ -2445,6 +2445,8 @@ class Sema final : public SemaBase {
///
/// \param FD The FieldDecl to apply the attribute to
/// \param E The count expression on the attribute
/// \param NestedTypeLevel The pointer indirection level where the attribute
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm... why does this have to be a certain number deep, and not just calculated/stored on that type?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Because the attribute is late parsed after the struct is fully declared, so the field's type is already constructed. We could use placeholder types and backfill later, but then we'd have to walk through all the type indirections. Or record the type positions that need the backfill anyway. Passing the level directly seemed simpler and avoids that overhead.

/// applies
/// \param CountInBytes If true the attribute is from the "sized_by" family of
/// attributes. If the false the attribute is from
/// "counted_by" family of attributes.
Expand All @@ -2457,7 +2459,8 @@ class Sema final : public SemaBase {
/// `counted_by_or_null` attribute.
///
/// \returns false iff semantically valid.
bool CheckCountedByAttrOnField(FieldDecl *FD, Expr *E, bool CountInBytes,
bool CheckCountedByAttrOnField(FieldDecl *FD, Expr *E,
unsigned NestedTypeLevel, bool CountInBytes,
bool OrNull);

/// Perform Bounds Safety Semantic checks for assigning to a `__counted_by` or
Expand Down Expand Up @@ -4198,7 +4201,8 @@ class Sema final : public SemaBase {

/// ActOnFinishDelayedAttribute - Invoked when we have finished parsing an
/// attribute for which parsing is delayed.
void ActOnFinishDelayedAttribute(Scope *S, Decl *D, ParsedAttributes &Attrs);
void ActOnFinishDelayedAttribute(Scope *S, Decl *D, ParsedAttributes &Attrs,
unsigned NestedTypeLevel = 0);

/// Diagnose any unused parameters in the given sequence of
/// ParmVarDecl pointers.
Expand Down Expand Up @@ -5071,7 +5075,8 @@ class Sema final : public SemaBase {
void ProcessDeclAttributeList(Scope *S, Decl *D,
const ParsedAttributesView &AttrList,
const ProcessDeclAttributeOptions &Options =
ProcessDeclAttributeOptions());
ProcessDeclAttributeOptions(),
unsigned NestedTypeLevel = 0);

/// Annotation attributes are the only attributes allowed after an access
/// specifier.
Expand Down
83 changes: 61 additions & 22 deletions clang/lib/Parse/ParseDecl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1943,7 +1943,11 @@ Parser::DeclGroupPtrTy Parser::ParseSimpleDeclaration(

ParsedTemplateInfo TemplateInfo;
DeclSpecContext DSContext = getDeclSpecContextFromDeclaratorContext(Context);
ParseDeclarationSpecifiers(DS, TemplateInfo, AS_none, DSContext);
// FIXME: Why is PSoon true?
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thats a wonderful question :)

LateParsedAttrList BoundsSafetyLateAttrs(
/*PSoon=*/true, /*LateAttrParseExperimentalExtOnly=*/true);
ParseDeclarationSpecifiers(DS, TemplateInfo, AS_none, DSContext,
&BoundsSafetyLateAttrs);

// If we had a free-standing type definition with a missing semicolon, we
// may get this far before the problem becomes obvious.
Expand Down Expand Up @@ -2725,12 +2729,12 @@ Decl *Parser::ParseDeclarationAfterDeclaratorAndAttributes(

void Parser::ParseSpecifierQualifierList(
DeclSpec &DS, ImplicitTypenameContext AllowImplicitTypename,
AccessSpecifier AS, DeclSpecContext DSC) {
AccessSpecifier AS, DeclSpecContext DSC, LateParsedAttrList *LateAttrs) {
ParsedTemplateInfo TemplateInfo;
/// specifier-qualifier-list is a subset of declaration-specifiers. Just
/// parse declaration-specifiers and complain about extra stuff.
/// TODO: diagnose attribute-specifiers and alignment-specifiers.
ParseDeclarationSpecifiers(DS, TemplateInfo, AS, DSC, nullptr,
ParseDeclarationSpecifiers(DS, TemplateInfo, AS, DSC, LateAttrs,
AllowImplicitTypename);

// Validate declspec for type-name.
Expand Down Expand Up @@ -3136,15 +3140,37 @@ void Parser::ParseAlignmentSpecifier(ParsedAttributes &Attrs,
}
}

void Parser::DistributeCLateParsedAttrs(Decl *Dcl,
void Parser::DistributeCLateParsedAttrs(Declarator &D, Decl *Dcl,
LateParsedAttrList *LateAttrs) {
if (!LateAttrs)
return;

unsigned NestedTypeLevel = 0;
for (unsigned i = 0; i < D.getNumTypeObjects(); ++i) {
DeclaratorChunk &DC = D.getTypeObject(i);

switch (DC.Kind) {
case DeclaratorChunk::Pointer:
case DeclaratorChunk::Array:
break;
default:
continue;
}

// Extract `LateParsedAttribute *` from `DeclaratorChunk`.
for (auto *OpaqueLA : DC.LateAttrList) {
auto *LA = static_cast<LateParsedAttribute *>(OpaqueLA);
LA->NestedTypeLevel = NestedTypeLevel;
LateAttrs->push_back(LA);
}
NestedTypeLevel++;
}

// Attach `Decl *` to each `LateParsedAttribute *`.
if (Dcl) {
for (auto *LateAttr : *LateAttrs) {
if (LateAttr->Decls.empty())
LateAttr->addDecl(Dcl);
for (auto *LA : *LateAttrs) {
if (LA->Decls.empty())
LA->addDecl(Dcl);
}
}
}
Expand Down Expand Up @@ -3217,12 +3243,6 @@ void Parser::ParseBoundsAttribute(IdentifierInfo &AttrName,
ArgExprs.push_back(ArgExpr.get());
Parens.consumeClose();

ASTContext &Ctx = Actions.getASTContext();

ArgExprs.push_back(IntegerLiteral::Create(
Ctx, llvm::APInt(Ctx.getTypeSize(Ctx.getSizeType()), 0),
Ctx.getSizeType(), SourceLocation()));

Attrs.addNew(&AttrName, SourceRange(AttrNameLoc, Parens.getCloseLocation()),
AttributeScopeInfo(), ArgExprs.data(), ArgExprs.size(), Form);
}
Expand Down Expand Up @@ -4706,7 +4726,8 @@ void Parser::ParseStructDeclaration(
MaybeParseCXX11Attributes(Attrs);

// Parse the common specifier-qualifiers-list piece.
ParseSpecifierQualifierList(DS);
ParseSpecifierQualifierList(DS, AS_none, DeclSpecContext::DSC_normal,
LateFieldAttrs);

// If there are no declarators, this is a free-standing declaration
// specifier. Let the actions module cope with it.
Expand Down Expand Up @@ -4768,7 +4789,7 @@ void Parser::ParseStructDeclaration(
// We're done with this declarator; invoke the callback.
Decl *Field = FieldsCallback(DeclaratorInfo);
if (Field)
DistributeCLateParsedAttrs(Field, LateFieldAttrs);
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.
Expand Down Expand Up @@ -4825,7 +4846,8 @@ void Parser::ParseLexedCAttribute(LateParsedAttribute &LA, bool EnterScope,
SourceLocation(), ParsedAttr::Form::GNU(), nullptr);

for (auto *D : LA.Decls)
Actions.ActOnFinishDelayedAttribute(getCurScope(), D, Attrs);
Actions.ActOnFinishDelayedAttribute(getCurScope(), D, Attrs,
LA.NestedTypeLevel);

// 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.
Expand Down Expand Up @@ -6129,7 +6151,8 @@ bool Parser::isConstructorDeclarator(bool IsUnqualified, bool DeductionGuide,

void Parser::ParseTypeQualifierListOpt(
DeclSpec &DS, unsigned AttrReqs, bool AtomicOrPtrauthAllowed,
bool IdentifierRequired, llvm::function_ref<void()> CodeCompletionHandler) {
bool IdentifierRequired, llvm::function_ref<void()> CodeCompletionHandler,
LateParsedAttrList *LateAttrs) {
if ((AttrReqs & AR_CXX11AttributesParsed) &&
isAllowedCXX11AttributeSpecifier()) {
ParsedAttributes Attrs(AttrFactory);
Expand Down Expand Up @@ -6271,7 +6294,9 @@ void Parser::ParseTypeQualifierListOpt(
// recovery is graceful.
if (AttrReqs & AR_GNUAttributesParsed ||
AttrReqs & AR_GNUAttributesParsedAndRejected) {
ParseGNUAttributes(DS.getAttributes());

assert(!LateAttrs || LateAttrs->lateAttrParseExperimentalExtOnly());
ParseGNUAttributes(DS.getAttributes(), LateAttrs);
continue; // do *not* consume the next token!
}
// otherwise, FALL THROUGH!
Expand Down Expand Up @@ -6452,21 +6477,35 @@ void Parser::ParseDeclaratorInternal(Declarator &D,
((D.getContext() != DeclaratorContext::CXXNew)
? AR_GNUAttributesParsed
: AR_GNUAttributesParsedAndRejected);
LateParsedAttrList LateAttrs(/*PSoon=*/true,
/*LateAttrParseExperimentalExtOnly=*/true);
ParseTypeQualifierListOpt(DS, Reqs, /*AtomicOrPtrauthAllowed=*/true,
!D.mayOmitIdentifier());
!D.mayOmitIdentifier(), {}, &LateAttrs);
D.ExtendWithDeclSpec(DS);

// Recursively parse the declarator.
Actions.runWithSufficientStackSpace(
D.getBeginLoc(), [&] { ParseDeclaratorInternal(D, DirectDeclParser); });
if (Kind == tok::star)
if (Kind == tok::star) {
DeclaratorChunk::LateAttrListTy OpaqueLateAttrList;
if (getLangOpts().ExperimentalLateParseAttributes && !LateAttrs.empty()) {
// TODO: Support `counted_by` in function parameters, return types, and
// other contexts (Issue #167365).
if (!D.isFunctionDeclarator()) {
for (LateParsedAttribute *LA : LateAttrs) {
OpaqueLateAttrList.push_back(LA);
}
}
LateAttrs.clear();
}
// Remember that we parsed a pointer type, and remember the type-quals.
D.AddTypeInfo(DeclaratorChunk::getPointer(
DS.getTypeQualifiers(), Loc, DS.getConstSpecLoc(),
DS.getVolatileSpecLoc(), DS.getRestrictSpecLoc(),
DS.getAtomicSpecLoc(), DS.getUnalignedSpecLoc()),
std::move(DS.getAttributes()), SourceLocation());
else
std::move(DS.getAttributes()), SourceLocation(),
OpaqueLateAttrList);
} else
// Remember that we parsed a Block type, and remember the type-quals.
D.AddTypeInfo(
DeclaratorChunk::getBlockPointer(DS.getTypeQualifiers(), Loc),
Expand Down
10 changes: 8 additions & 2 deletions clang/lib/Sema/SemaBoundsSafety.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -50,8 +50,8 @@ enum class CountedByInvalidPointeeTypeKind {
VALID,
};

bool Sema::CheckCountedByAttrOnField(FieldDecl *FD, Expr *E, bool CountInBytes,
bool OrNull) {
bool Sema::CheckCountedByAttrOnField(FieldDecl *FD, Expr *E, unsigned Level,
bool CountInBytes, bool OrNull) {
// Check the context the attribute is used in

unsigned Kind = getCountAttrKind(CountInBytes, OrNull);
Expand All @@ -62,6 +62,12 @@ bool Sema::CheckCountedByAttrOnField(FieldDecl *FD, Expr *E, bool CountInBytes,
return true;
}

if (Level != 0) {
Diag(FD->getBeginLoc(), diag::err_counted_by_on_nested_pointer)
<< Kind << FD->getSourceRange();
return true;
}

const auto FieldTy = FD->getType();
if (FieldTy->isArrayType() && (CountInBytes || OrNull)) {
Diag(FD->getBeginLoc(),
Expand Down
6 changes: 4 additions & 2 deletions clang/lib/Sema/SemaDecl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -16872,11 +16872,13 @@ Decl *Sema::ActOnFinishFunctionBody(Decl *dcl, Stmt *Body, bool IsInstantiation,
/// When we finish delayed parsing of an attribute, we must attach it to the
/// relevant Decl.
void Sema::ActOnFinishDelayedAttribute(Scope *S, Decl *D,
ParsedAttributes &Attrs) {
ParsedAttributes &Attrs,
unsigned NestedTypeLevel) {
// Always attach attributes to the underlying decl.
if (TemplateDecl *TD = dyn_cast<TemplateDecl>(D))
D = TD->getTemplatedDecl();
ProcessDeclAttributeList(S, D, Attrs);
ProcessDeclAttributeList(S, D, Attrs, ProcessDeclAttributeOptions(),
NestedTypeLevel);
ProcessAPINotes(D);

if (CXXMethodDecl *Method = dyn_cast_or_null<CXXMethodDecl>(D))
Expand Down
Loading
Loading