Skip to content

[BoundsSafety] Parse external bounds attributes in ObjC methods #10068

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Mar 28, 2025
Merged
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
9 changes: 7 additions & 2 deletions clang/include/clang/Parse/Parser.h
Original file line number Diff line number Diff line change
Expand Up @@ -1382,7 +1382,10 @@ class Parser : public CodeCompletionHandler {

void ParseLexedAttributes() override;

void addDecl(Decl *D) { Decls.push_back(D); }
void addDecl(Decl *D) {
assert(D && "cannot add null decl!");
Decls.push_back(D);
}
};

/// Contains the lexed tokens of a pragma with arguments that
Expand Down Expand Up @@ -1821,8 +1824,10 @@ class Parser : public CodeCompletionHandler {

bool isTokIdentifier_in() const;

// TO_UPSTREAM(BoundsSafety) Added LateParsedAttrs
ParsedType ParseObjCTypeName(ObjCDeclSpec &DS, DeclaratorContext Ctx,
ParsedAttributes *ParamAttrs);
ParsedAttributes *ParamAttrs,
LateParsedAttrList *LateParsedAttrs = nullptr);
Decl *ParseObjCMethodPrototype(
tok::ObjCKeywordKind MethodImplKind = tok::objc_not_keyword,
bool MethodDefinition = true);
Expand Down
80 changes: 70 additions & 10 deletions clang/lib/Parse/ParseObjc.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1298,9 +1298,11 @@ static void takeDeclAttributes(ParsedAttributes &attrs,
/// '(' objc-type-qualifiers[opt] type-name ')'
/// '(' objc-type-qualifiers[opt] ')'
///
/// TO_UPSTREAM(BoundsSafety) Added LateParsedAttrs
ParsedType Parser::ParseObjCTypeName(ObjCDeclSpec &DS,
DeclaratorContext context,
ParsedAttributes *paramAttrs) {
ParsedAttributes *paramAttrs,
LateParsedAttrList *LateParsedAttrs) {
assert(context == DeclaratorContext::ObjCParameter ||
context == DeclaratorContext::ObjCResult);
assert((paramAttrs != nullptr) ==
Expand All @@ -1325,9 +1327,10 @@ ParsedType Parser::ParseObjCTypeName(ObjCDeclSpec &DS,
DeclSpecContext dsContext = DeclSpecContext::DSC_normal;
if (context == DeclaratorContext::ObjCResult)
dsContext = DeclSpecContext::DSC_objc_method_result;
ParseSpecifierQualifierList(declSpec, AS_none, dsContext);
ParseSpecifierQualifierList(declSpec, AS_none, dsContext, LateParsedAttrs);
Declarator declarator(declSpec, ParsedAttributesView::none(), context);
ParseDeclarator(declarator);
DistributeCLateParsedAttrs(declarator, nullptr, LateParsedAttrs);

// If that's not invalid, extract a type.
if (!declarator.isInvalidType()) {
Expand Down Expand Up @@ -1406,17 +1409,25 @@ Decl *Parser::ParseObjCMethodDecl(SourceLocation mLoc,
return nullptr;
}

/* TO_UPSTREAM(BoundsSafety) ON */
LateParsedAttrList LateParsedAttrs(/*PSoon=*/true,
/*LateAttrParseExperimentalExtOnly=*/true);
LateParsedAttrList LateParsedReturnAttrs(
/*PSoon=*/false,
/*LateAttrParseExperimentalExtOnly=*/true);
/* TO_UPSTREAM(BoundsSafety) OFF */

// Parse the return type if present.
ParsedType ReturnType;
ObjCDeclSpec DSRet;
if (Tok.is(tok::l_paren))
ReturnType =
ParseObjCTypeName(DSRet, DeclaratorContext::ObjCResult, nullptr);
ReturnType = ParseObjCTypeName(DSRet, DeclaratorContext::ObjCResult,
nullptr, &LateParsedReturnAttrs);

// If attributes exist before the method, parse them.
ParsedAttributes methodAttrs(AttrFactory);
MaybeParseAttributes(PAKM_CXX11 | (getLangOpts().ObjC ? PAKM_GNU : 0),
methodAttrs);
methodAttrs, &LateParsedReturnAttrs);

if (Tok.is(tok::code_completion)) {
cutOffParsing();
Expand Down Expand Up @@ -1450,33 +1461,51 @@ Decl *Parser::ParseObjCMethodDecl(SourceLocation mLoc,
selLoc, Sel, nullptr, CParamInfo.data(), CParamInfo.size(), methodAttrs,
MethodImplKind, false, MethodDefinition);
PD.complete(Result);
/* TO_UPSTREAM(BoundsSafety) ON */
if (Result) {
for (auto *LateAttr : LateParsedReturnAttrs) {
// there are no parameters with late attrs to parse
assert(LateAttr->Decls.empty());
LateAttr->addDecl(Result);
ParseLexedCAttribute(*LateAttr, true);
}
}
/* TO_UPSTREAM(BoundsSafety) OFF */
return Result;
}

SmallVector<const IdentifierInfo *, 12> KeyIdents;
SmallVector<SourceLocation, 12> KeyLocs;
SmallVector<SemaObjC::ObjCArgInfo, 12> ArgInfos;
/* TO_UPSTREAM(BoundsSafety) ON */
SmallVector<LateParsedAttrList, 12> LateParamAttrs;
/* TO_UPSTREAM(BoundsSafety) OFF */
ParseScope PrototypeScope(this, Scope::FunctionPrototypeScope |
Scope::FunctionDeclarationScope | Scope::DeclScope);

AttributePool allParamAttrs(AttrFactory);
while (true) {
ParsedAttributes paramAttrs(AttrFactory);
SemaObjC::ObjCArgInfo ArgInfo;
/* TO_UPSTREAM(BoundsSafety) ON */
LateParsedAttrList LateAttrs(/*PSoon*/ false,
/*LateAttrParseExperimentalExtOnly*/ true);
/* TO_UPSTREAM(BoundsSafety) OFF */

// Each iteration parses a single keyword argument.
if (ExpectAndConsume(tok::colon))
break;

ArgInfo.Type = nullptr;
if (Tok.is(tok::l_paren)) // Parse the argument type if present.
ArgInfo.Type = ParseObjCTypeName(
ArgInfo.DeclSpec, DeclaratorContext::ObjCParameter, &paramAttrs);
ArgInfo.Type =
ParseObjCTypeName(ArgInfo.DeclSpec, DeclaratorContext::ObjCParameter,
&paramAttrs, &LateAttrs);

// If attributes exist before the argument name, parse them.
// Regardless, collect all the attributes we've parsed so far.
MaybeParseAttributes(PAKM_CXX11 | (getLangOpts().ObjC ? PAKM_GNU : 0),
paramAttrs);
paramAttrs, &LateAttrs);
ArgInfo.ArgAttrs = paramAttrs;

// Code completion for the next piece of the selector.
Expand All @@ -1497,6 +1526,7 @@ Decl *Parser::ParseObjCMethodDecl(SourceLocation mLoc,
ConsumeToken(); // Eat the identifier.

ArgInfos.push_back(ArgInfo);
LateParamAttrs.push_back(LateAttrs); // TO_UPSTREAM(BoundsSafety)
KeyIdents.push_back(SelIdent);
KeyLocs.push_back(selLoc);

Expand Down Expand Up @@ -1543,13 +1573,23 @@ Decl *Parser::ParseObjCMethodDecl(SourceLocation mLoc,
}
DeclSpec DS(AttrFactory);
ParsedTemplateInfo TemplateInfo;
ParseDeclarationSpecifiers(DS, TemplateInfo);
ParseDeclarationSpecifiers(
DS, TemplateInfo,
/* AccessSpecifier AS */ AS_none,
/* DeclSpecContext DSC */ DeclSpecContext::DSC_normal,
&LateParsedAttrs);
// Parse the declarator.
Declarator ParmDecl(DS, ParsedAttributesView::none(),
DeclaratorContext::Prototype);
ParseDeclarator(ParmDecl);
const IdentifierInfo *ParmII = ParmDecl.getIdentifier();
Decl *Param = Actions.ActOnParamDeclarator(getCurScope(), ParmDecl);
/* TO_UPSTREAM(BoundsSafety) ON */
// This will add Param to any late attrs that do not already have an
// assigned decl, so it's important that other late attrs are not mixed
// in to LateParsedAttrs without a decl at this point
DistributeCLateParsedAttrs(ParmDecl, Param, &LateParsedAttrs);
/* TO_UPSTREAM(BoundsSafety) OFF */
CParamInfo.push_back(DeclaratorChunk::ParamInfo(ParmII,
ParmDecl.getIdentifierLoc(),
Param,
Expand All @@ -1561,9 +1601,18 @@ Decl *Parser::ParseObjCMethodDecl(SourceLocation mLoc,
// instance, if a method declares a parameter called "id", that parameter must
// not shadow the "id" type.)
SmallVector<ParmVarDecl *, 12> ObjCParamInfo;
for (auto &ArgInfo : ArgInfos) {
for (const auto &[ArgInfo, LateAttrs] : llvm::zip(ArgInfos, LateParamAttrs)) {
ParmVarDecl *Param = Actions.ObjC().ActOnMethodParmDeclaration(
getCurScope(), ArgInfo, ObjCParamInfo.size(), MethodDefinition);
/* TO_UPSTREAM(BoundsSafety) ON */
if (Param) {
for (auto *LateAttr : LateAttrs) {
assert(LateAttr->Decls.empty());
LateAttr->addDecl(Param);
}
LateParsedAttrs.append(LateAttrs);
}
/* TO_UPSTREAM(BoundsSafety) OFF */
ObjCParamInfo.push_back(Param);
}

Expand All @@ -1581,6 +1630,17 @@ Decl *Parser::ParseObjCMethodDecl(SourceLocation mLoc,
getCurScope(), mLoc, Tok.getLocation(), mType, DSRet, ReturnType, KeyLocs,
Sel, ObjCParamInfo.data(), CParamInfo.data(), CParamInfo.size(),
methodAttrs, MethodImplKind, isVariadic, MethodDefinition);
/* TO_UPSTREAM(BoundsSafety) ON */
if (Result) {
for (auto *LateAttr : LateParsedReturnAttrs) {
assert(LateAttr->Decls.empty());
LateAttr->addDecl(Result);
}
LateParsedAttrs.append(LateParsedReturnAttrs);
ParseLexedCAttributeList(LateParsedAttrs,
/*we already have parameters in scope*/ false);
}
/* TO_UPSTREAM(BoundsSafety) OFF */

PD.complete(Result);
return Result;
Expand Down
127 changes: 67 additions & 60 deletions clang/lib/Sema/SemaDeclAttr.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -6451,6 +6451,7 @@ class DynamicBoundsAttrInfo {
TypedefNameDecl *TND;
ValueDecl *VD;
VarDecl *Var;
ObjCMethodDecl *ObjCMethod;
QualType DeclTy;
QualType Ty;
unsigned EffectiveLevel;
Expand All @@ -6462,7 +6463,13 @@ class DynamicBoundsAttrInfo {
TND = dyn_cast<TypedefNameDecl>(D);
VD = dyn_cast<ValueDecl>(D);
Var = dyn_cast<VarDecl>(D);
DeclTy = TND ? TND->getUnderlyingType() : VD->getType();
ObjCMethod = dyn_cast<ObjCMethodDecl>(D);
if (TND)
DeclTy = TND->getUnderlyingType();
else if (ObjCMethod)
DeclTy = ObjCMethod->getReturnType();
else
DeclTy = VD->getType();
IsFPtr = false;
EffectiveLevel = Level;
Ty = DeclTy;
Expand Down Expand Up @@ -6567,72 +6574,69 @@ void Sema::applyPtrCountedByEndedByAttr(Decl *D, unsigned Level,
}
}

if (Info.VD) {
const auto *FD = dyn_cast<FieldDecl>(Info.VD);
if (FD && FD->getParent()->isUnion()) {
Diag(Loc, diag::err_invalid_decl_kind_bounds_safety_union_count)
<< DiagName;
return;
}
const auto *FD = dyn_cast<FieldDecl>(D);
if (FD && FD->getParent()->isUnion()) {
Diag(Loc, diag::err_invalid_decl_kind_bounds_safety_union_count)
<< DiagName;
return;
}

if (Info.EffectiveLevel != 0 &&
(!isa<ParmVarDecl>(Info.VD) || Info.DeclTy->isBoundsAttributedType())) {
Diag(Loc, diag::err_bounds_safety_nested_dynamic_bound) << DiagName;
return;
}
if (Info.EffectiveLevel != 0 &&
(!isa<ParmVarDecl>(D) || Info.DeclTy->isBoundsAttributedType())) {
Diag(Loc, diag::err_bounds_safety_nested_dynamic_bound) << DiagName;
return;
}

// Clang causes array parameters to decay to pointers so quickly that
// attributes aren't even parsed yet. This causes arrays with both an
// explicit size and a count attribute to go to the CountAttributedType
// case of ConstructCountAttributedType, which complains that the type
// has two count attributes. See if we can produce a better diagnostic here
// instead.
if (const auto *PVD = dyn_cast_or_null<ParmVarDecl>(Info.Var)) {
QualType TSITy = PVD->getTypeSourceInfo()->getType();
if (IsEndedBy) {
if (Level == 0 && TSITy->isArrayType()) {
Diag(Loc, diag::err_attribute_pointers_only) << DiagName << 0;
return;
}
} else {
const auto *ATy = Context.getAsArrayType(TSITy);
if (Level == 0 && ATy && !ATy->isIncompleteArrayType() &&
!TSITy->hasAttr(attr::ArrayDecayDiscardsCountInParameters)) {
Diag(Loc, diag::err_bounds_safety_complete_array_with_count);
return;
}
// Clang causes array parameters to decay to pointers so quickly that
// attributes aren't even parsed yet. This causes arrays with both an
// explicit size and a count attribute to go to the CountAttributedType
// case of ConstructCountAttributedType, which complains that the type
// has two count attributes. See if we can produce a better diagnostic here
// instead.
if (const auto *PVD = dyn_cast<ParmVarDecl>(D)) {
QualType TSITy = PVD->getTypeSourceInfo()->getType();
if (IsEndedBy) {
if (Level == 0 && TSITy->isArrayType()) {
Diag(Loc, diag::err_attribute_pointers_only) << DiagName << 0;
return;
}
} else {
const auto *ATy = Context.getAsArrayType(TSITy);
if (Level == 0 && ATy && !ATy->isIncompleteArrayType() &&
!TSITy->hasAttr(attr::ArrayDecayDiscardsCountInParameters)) {
Diag(Loc, diag::err_bounds_safety_complete_array_with_count);
return;
}
}
}

if (Info.Ty->isArrayType() && OrNull &&
(FD || Info.EffectiveLevel > 0 ||
(Info.Var && Info.Var->hasExternalStorage()))) {
auto ErrDiag = Diag(Loc, diag::err_bounds_safety_nullable_fam);
// Pointers to dynamic count types are only allowed for parameters, so any
// FieldDecl containing a dynamic count type is a FAM. I.e. a struct field
// with type 'int(*)[__counted_by(...)]' is not valid.
ErrDiag << CountInBytes << /*is FAM?*/ !!FD << DiagName;
assert(!FD || Info.EffectiveLevel == 0);
if (Info.Ty->isArrayType() && OrNull &&
(FD || Info.EffectiveLevel > 0 ||
(Info.Var && Info.Var->hasExternalStorage()))) {
auto ErrDiag = Diag(Loc, diag::err_bounds_safety_nullable_fam);
// Pointers to dynamic count types are only allowed for parameters, so any
// FieldDecl containing a dynamic count type is a FAM. I.e. a struct field
// with type 'int(*)[__counted_by(...)]' is not valid.
ErrDiag << CountInBytes << /*is FAM?*/ !!FD << DiagName;
assert(!FD || Info.EffectiveLevel == 0);

SourceLocation FixItLoc = getSourceManager().getExpansionLoc(Loc);
SourceLocation EndLoc =
Lexer::getLocForEndOfToken(FixItLoc, /* Don't include '(' */ -1,
getSourceManager(), getLangOpts());
std::string Attribute = CountInBytes ? "__sized_by" : "__counted_by";
ErrDiag << FixItHint::CreateReplacement({FixItLoc, EndLoc}, Attribute);
SourceLocation FixItLoc = getSourceManager().getExpansionLoc(Loc);
SourceLocation EndLoc =
Lexer::getLocForEndOfToken(FixItLoc, /* Don't include '(' */ -1,
getSourceManager(), getLangOpts());
std::string Attribute = CountInBytes ? "__sized_by" : "__counted_by";
ErrDiag << FixItHint::CreateReplacement({FixItLoc, EndLoc}, Attribute);

return;
}
return;
}

if (Info.Ty->isArrayType() && Info.EffectiveLevel > 0) {
auto ErrDiag =
Diag(
Loc,
diag::
err_bounds_safety_unsupported_address_of_incomplete_array_type)
<< Info.Ty;
// apply attribute anyways to avoid too misleading follow-up diagnostics
}
if (Info.Ty->isArrayType() && Info.EffectiveLevel > 0) {
auto ErrDiag =
Diag(Loc,
diag::
err_bounds_safety_unsupported_address_of_incomplete_array_type)
<< Info.Ty;
// apply attribute anyways to avoid too misleading follow-up diagnostics
}

QualType NewDeclTy{};
Expand All @@ -6654,8 +6658,9 @@ void Sema::applyPtrCountedByEndedByAttr(Decl *D, unsigned Level,
// Make started_by() pointers if VD is a field or variable. We don't want to
// create started_by(X) pointers where X is a function etc.
std::optional<TypeCoupledDeclRefInfo> StartPtrInfo;
if (Info.VD && (isa<FieldDecl>(Info.VD) || isa<VarDecl>(Info.VD))) {
if (isa<FieldDecl, VarDecl>(D)) {
assert(Level <= 1);
assert(Info.VD);
StartPtrInfo = TypeCoupledDeclRefInfo(Info.VD, /*Deref=*/Level != 0);
}

Expand Down Expand Up @@ -6711,6 +6716,8 @@ void Sema::applyPtrCountedByEndedByAttr(Decl *D, unsigned Level,
Info.TND->getTypeSourceInfo()->getTypeLoc().getBeginLoc();
TypeSourceInfo *TSI = Context.getTrivialTypeSourceInfo(NewDeclTy, Loc);
Info.TND->setTypeSourceInfo(TSI);
} else if (Info.ObjCMethod) {
Info.ObjCMethod->setReturnType(NewDeclTy);
} else {
Info.VD->setType(NewDeclTy);
// Reconstruct implicit cast for initializer after variable type change.
Expand Down
Loading