Skip to content

Commit e0ee8ec

Browse files
[-Wunsafe-buffer-usage] Check for too complex count-attributed assignments
This is an initial part of an analysis of count-attributed assignment groups. This commit adds an AST visitor that is responsible for finding bounds-attributed assignment groups and assignments to bounds-attributed objects (pointers and dependent counts) that are too complex to verify. As a PoC, this commit adds checks for too complex assignments, which are assignments that are not directly inside of a compound statement (like other assignment groups) and modify the pointer or count in some way. Our model rejects those and requires the user to simplify their code. For example: ``` void foo(int *__counted_by(count) p, int count) { q = p = ...; ^ this is rejected n = count = ...; ^ this is rejected // the following is fine: p = ...; count = ...; } ``` rdar://161607826
1 parent 0718952 commit e0ee8ec

File tree

5 files changed

+473
-3
lines changed

5 files changed

+473
-3
lines changed

clang/include/clang/Analysis/Analyses/UnsafeBufferUsage.h

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,13 @@ class UnsafeBufferUsageHandler {
142142
ASTContext &Ctx) {
143143
handleUnsafeOperation(Arg, IsRelatedToDecl, Ctx);
144144
}
145+
146+
virtual void handleTooComplexCountAttributedAssign(const Expr *E,
147+
const ValueDecl *VD,
148+
bool IsRelatedToDecl,
149+
ASTContext &Ctx) {
150+
handleUnsafeOperation(E, IsRelatedToDecl, Ctx);
151+
}
145152
/* TO_UPSTREAM(BoundsSafety) OFF */
146153

147154
/// Invoked when a fix is suggested against a variable. This function groups
@@ -196,7 +203,8 @@ class UnsafeBufferUsageHandler {
196203
// This function invokes the analysis and allows the caller to react to it
197204
// through the handler class.
198205
void checkUnsafeBufferUsage(const Decl *D, UnsafeBufferUsageHandler &Handler,
199-
bool EmitSuggestions);
206+
bool EmitSuggestions,
207+
bool BoundsSafetyAttributesEnabled = false);
200208

201209
namespace internal {
202210
// Tests if any two `FixItHint`s in `FixIts` conflict. Two `FixItHint`s

clang/include/clang/Basic/DiagnosticSemaKinds.td

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14258,6 +14258,10 @@ def note_unsafe_count_attributed_pointer_argument_null_to_nonnull : Note<
1425814258
def warn_unsafe_single_pointer_argument : Warning<
1425914259
"unsafe assignment to function parameter of __single pointer type">,
1426014260
InGroup<UnsafeBufferUsage>, DefaultIgnore;
14261+
def warn_assign_to_count_attributed_must_be_simple_stmt : Warning<
14262+
"assignment to %select{count-attributed pointer|dependent count}0 '%1' "
14263+
"must be a simple statement '%1 = ...'">,
14264+
InGroup<UnsafeBufferUsage>, DefaultIgnore;
1426114265
#ifndef NDEBUG
1426214266
// Not a user-facing diagnostic. Useful for debugging false negatives in
1426314267
// -fsafe-buffer-usage-suggestions (i.e. lack of -Wunsafe-buffer-usage fixits).

clang/lib/Analysis/UnsafeBufferUsage.cpp

Lines changed: 319 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5444,9 +5444,322 @@ static void applyGadgets(const Decl *D, FixableGadgetList FixableGadgets,
54445444
}
54455445
}
54465446

5447+
// Checks if Self and Other are the same member bases. This supports only very
5448+
// simple forms of member bases.
5449+
static bool isSameMemberBase(const Expr *Self, const Expr *Other) {
5450+
for (;;) {
5451+
if (Self == Other)
5452+
return true;
5453+
5454+
const auto *SelfICE = dyn_cast<ImplicitCastExpr>(Self);
5455+
const auto *OtherICE = dyn_cast<ImplicitCastExpr>(Other);
5456+
if (SelfICE && OtherICE && SelfICE->getCastKind() == CK_LValueToRValue &&
5457+
OtherICE->getCastKind() == CK_LValueToRValue) {
5458+
Self = SelfICE->getSubExpr();
5459+
Other = OtherICE->getSubExpr();
5460+
}
5461+
5462+
const auto *SelfDRE = dyn_cast<DeclRefExpr>(Self);
5463+
const auto *OtherDRE = dyn_cast<DeclRefExpr>(Other);
5464+
if (SelfDRE && OtherDRE)
5465+
return SelfDRE->getDecl() == OtherDRE->getDecl();
5466+
5467+
const auto *SelfME = dyn_cast<MemberExpr>(Self);
5468+
const auto *OtherME = dyn_cast<MemberExpr>(Other);
5469+
if (!SelfME || !OtherME ||
5470+
SelfME->getMemberDecl() != OtherME->getMemberDecl()) {
5471+
return false;
5472+
}
5473+
5474+
Self = SelfME->getBase();
5475+
Other = OtherME->getBase();
5476+
}
5477+
}
5478+
5479+
using DependentDeclSetTy = llvm::SmallPtrSet<const ValueDecl *, 4>;
5480+
5481+
// Gets the set/closure of bounds-attributed pointers and counts that belong to
5482+
// the same group. Consider the following example:
5483+
// int a, b, c;
5484+
// int *__counted_by(a + b) p;
5485+
// int *__counted_by(b - c) q;
5486+
// Passing any of the variables above as `InitVD`, the function should return
5487+
// the closure `{a, b, c, p, q}`.
5488+
static DependentDeclSetTy getBoundsAttributedClosure(const ValueDecl *InitVD) {
5489+
DependentDeclSetTy Set;
5490+
5491+
llvm::SmallVector<const ValueDecl *, 4> WorkList;
5492+
WorkList.push_back(InitVD);
5493+
5494+
while (!WorkList.empty()) {
5495+
const ValueDecl *CurVD = WorkList.pop_back_val();
5496+
bool Inserted = Set.insert(CurVD).second;
5497+
if (!Inserted)
5498+
continue;
5499+
5500+
// If CurVD is a dependent decl, add the pointers that depend on CurVD.
5501+
for (const auto *Attr : CurVD->specific_attrs<DependerDeclsAttr>()) {
5502+
for (const Decl *D : Attr->dependerDecls()) {
5503+
if (const auto *VD = dyn_cast<ValueDecl>(D))
5504+
WorkList.push_back(VD);
5505+
}
5506+
}
5507+
5508+
// If CurVD is a bounds-attributed pointer (or pointer to it), add its
5509+
// dependent decls.
5510+
QualType Ty = CurVD->getType();
5511+
const auto *BAT = Ty->getAs<BoundsAttributedType>();
5512+
if (!BAT && Ty->isPointerType())
5513+
BAT = Ty->getPointeeType()->getAs<BoundsAttributedType>();
5514+
if (BAT) {
5515+
for (const auto &DI : BAT->dependent_decls())
5516+
WorkList.push_back(DI.getDecl());
5517+
}
5518+
}
5519+
5520+
return Set;
5521+
}
5522+
5523+
// Bounds-attributed pointer or dependent count.
5524+
struct BoundsAttributedObject {
5525+
const ValueDecl *Decl = nullptr;
5526+
const Expr *MemberBase = nullptr;
5527+
int DerefLevel = 0;
5528+
};
5529+
5530+
static std::optional<BoundsAttributedObject>
5531+
getBoundsAttributedObject(const Expr *E) {
5532+
E = E->IgnoreParenCasts();
5533+
5534+
int DerefLevel = 0;
5535+
while (const auto *UO = dyn_cast<UnaryOperator>(E)) {
5536+
if (UO->getOpcode() == UO_Deref)
5537+
DerefLevel++;
5538+
else if (UO->getOpcode() == UO_AddrOf)
5539+
DerefLevel--;
5540+
else
5541+
break;
5542+
E = UO->getSubExpr()->IgnoreParenCasts();
5543+
}
5544+
assert(DerefLevel >= 0);
5545+
5546+
const ValueDecl *Decl;
5547+
const Expr *MemberBase = nullptr;
5548+
5549+
if (const auto *DRE = dyn_cast<DeclRefExpr>(E))
5550+
Decl = DRE->getDecl();
5551+
else if (const auto *ME = dyn_cast<MemberExpr>(E)) {
5552+
Decl = ME->getMemberDecl();
5553+
MemberBase = ME->getBase();
5554+
} else
5555+
return std::nullopt;
5556+
5557+
QualType Ty = Decl->getType();
5558+
bool IsBoundsAttributedPointer =
5559+
Ty->isBoundsAttributedType() ||
5560+
(Ty->isPointerType() && Ty->getPointeeType()->isBoundsAttributedType());
5561+
if (IsBoundsAttributedPointer || Decl->isDependentCount())
5562+
return {{Decl, MemberBase, DerefLevel}};
5563+
5564+
return std::nullopt;
5565+
}
5566+
5567+
struct BoundsAttributedAssignmentGroup {
5568+
DependentDeclSetTy DeclClosure;
5569+
llvm::SmallVector<const BinaryOperator *, 4> Assignments;
5570+
llvm::SmallVector<BoundsAttributedObject, 4> AssignedObjects;
5571+
using DeclUseTy = std::pair<const ValueDecl *, const Expr *>;
5572+
const Expr *MemberBase = nullptr;
5573+
5574+
void init(const BoundsAttributedObject &Object) {
5575+
DeclClosure = getBoundsAttributedClosure(Object.Decl);
5576+
MemberBase = Object.MemberBase;
5577+
}
5578+
5579+
void clear() {
5580+
DeclClosure.clear();
5581+
Assignments.clear();
5582+
AssignedObjects.clear();
5583+
MemberBase = nullptr;
5584+
}
5585+
5586+
bool empty() const {
5587+
return DeclClosure.empty();
5588+
}
5589+
5590+
bool isPartOfGroup(const BoundsAttributedObject &Object) const {
5591+
if (!DeclClosure.contains(Object.Decl))
5592+
return false;
5593+
if (MemberBase)
5594+
return Object.MemberBase &&
5595+
isSameMemberBase(MemberBase, Object.MemberBase);
5596+
return true;
5597+
}
5598+
5599+
void addAssignment(const BoundsAttributedObject &Object,
5600+
const BinaryOperator *BO) {
5601+
Assignments.push_back(BO);
5602+
AssignedObjects.push_back(Object);
5603+
}
5604+
};
5605+
5606+
// Visitor that is responsible for finding bounds-attributed assignment groups
5607+
// and other assignments that can modify bounds-attributed objects.
5608+
//
5609+
// A bounds-attributed group must be a group of assignments that are children
5610+
// of a CompoundStmt:
5611+
// void foo(int *__counted_by(count) p, int count) {
5612+
// p = ...;
5613+
// count = ...;
5614+
// }
5615+
// Here, the group consists of both assignments to p and count. Note that the
5616+
// function body is a CompoundStmt.
5617+
//
5618+
// We consider assignments to bounds-attributed objects that are not simple
5619+
// statements or not children of a CompoundStmt as too complex, and we give up
5620+
// trying to put them in a bounds-attributed group:
5621+
// void foo(int *__counted_by(count) p, int count) {
5622+
// q = p = ...;
5623+
// ^ too complex assignment to __counted_by() pointer
5624+
// n = count += ...;
5625+
// ^ too complex assignment to dependent count
5626+
// }
5627+
// Note that while those assignments are descendants of a CompoundStmt, they
5628+
// are not its direct children.
5629+
struct BoundsAttributedGroupFinder
5630+
: public ConstStmtVisitor<BoundsAttributedGroupFinder> {
5631+
using GroupHandlerTy = void(const BoundsAttributedAssignmentGroup &Group);
5632+
using AssignHandlerTy = void(const Expr *, const ValueDecl *);
5633+
std::function<GroupHandlerTy> GroupHandler;
5634+
std::function<AssignHandlerTy> TooComplexAssignHandler;
5635+
BoundsAttributedAssignmentGroup CurGroup;
5636+
5637+
explicit BoundsAttributedGroupFinder(
5638+
std::function<GroupHandlerTy> GroupHandler,
5639+
std::function<AssignHandlerTy> TooComplexAssignHandler)
5640+
: GroupHandler(std::move(GroupHandler)),
5641+
TooComplexAssignHandler(std::move(TooComplexAssignHandler)) {}
5642+
5643+
void VisitChildren(const Stmt *S) {
5644+
for (const Stmt *Child : S->children()) {
5645+
if (Child)
5646+
Visit(Child);
5647+
}
5648+
}
5649+
5650+
void VisitStmt(const Stmt *S) { VisitChildren(S); }
5651+
5652+
void VisitCompoundStmt(const CompoundStmt *CS) {
5653+
for (const Stmt *Child : CS->children()) {
5654+
const Stmt *E = Child;
5655+
5656+
// See through `ExprWithCleanups`. Clang will attach those nodes when C++
5657+
// temporary object needs to be materialized. In our case, this can
5658+
// happen when we create a temporary span with `sp.first()`. Then, the
5659+
// structure is going to be:
5660+
// CompoundStmt
5661+
// `-ExprWithCleanups
5662+
// `-BinaryOperator ...
5663+
if (const auto *EWC = dyn_cast<ExprWithCleanups>(E))
5664+
E = EWC->getSubExpr();
5665+
5666+
const auto *BO = dyn_cast<BinaryOperator>(E);
5667+
if (BO && BO->getOpcode() == BO_Assign)
5668+
handleAssignInCompound(BO);
5669+
else {
5670+
finishGroup();
5671+
Visit(Child);
5672+
}
5673+
}
5674+
5675+
finishGroup();
5676+
}
5677+
5678+
void handleAssignInCompound(const BinaryOperator *AssignOp) {
5679+
const auto ObjectOpt = getBoundsAttributedObject(AssignOp->getLHS());
5680+
if (!ObjectOpt.has_value()) {
5681+
finishGroup();
5682+
VisitChildren(AssignOp);
5683+
return;
5684+
}
5685+
5686+
if (!CurGroup.isPartOfGroup(*ObjectOpt)) {
5687+
finishGroup();
5688+
CurGroup.init(*ObjectOpt);
5689+
}
5690+
5691+
CurGroup.addAssignment(*ObjectOpt, AssignOp);
5692+
VisitChildren(AssignOp->getRHS());
5693+
}
5694+
5695+
void finishGroup() {
5696+
if (CurGroup.empty())
5697+
return;
5698+
GroupHandler(CurGroup);
5699+
CurGroup.clear();
5700+
}
5701+
5702+
// Handle statements that might modify bounds-attributed pointer/count, but
5703+
// are not children of a CompoundStmt.
5704+
5705+
void VisitBinaryOperator(const BinaryOperator *BO) {
5706+
VisitChildren(BO);
5707+
5708+
if (BO->isAssignmentOp())
5709+
checkTooComplexAssign(BO, BO->getLHS());
5710+
}
5711+
5712+
void VisitUnaryOperator(const UnaryOperator *UO) {
5713+
VisitChildren(UO);
5714+
5715+
if (UO->isIncrementDecrementOp())
5716+
checkTooComplexAssign(UO, UO->getSubExpr());
5717+
}
5718+
5719+
void checkTooComplexAssign(const Expr *E, const Expr *Sub) {
5720+
const auto DA = getBoundsAttributedObject(Sub);
5721+
if (DA.has_value())
5722+
TooComplexAssignHandler(E, DA->Decl);
5723+
}
5724+
};
5725+
5726+
static void
5727+
diagnoseTooComplexAssignToBoundsAttributed(const Expr *E, const ValueDecl *VD,
5728+
UnsafeBufferUsageHandler &Handler,
5729+
ASTContext &Ctx) {
5730+
// Don't diagnose pointer arithmetic, since -Wunsafe-buffer-usage already does
5731+
// it.
5732+
bool IsPtrArith = false;
5733+
if (E->getType()->isPointerType()) {
5734+
if (const auto *BO = dyn_cast<BinaryOperator>(E))
5735+
IsPtrArith = BO->isCompoundAssignmentOp();
5736+
else if (const auto *UO = dyn_cast<UnaryOperator>(E)) {
5737+
assert(UO->isIncrementDecrementOp());
5738+
IsPtrArith = true;
5739+
}
5740+
}
5741+
if (!IsPtrArith)
5742+
Handler.handleTooComplexCountAttributedAssign(
5743+
E, VD, /*IsRelatedToDecl=*/false, Ctx);
5744+
}
5745+
5746+
static void checkBoundsSafetyAssignments(const Stmt *S,
5747+
UnsafeBufferUsageHandler &Handler,
5748+
ASTContext &Ctx) {
5749+
BoundsAttributedGroupFinder Finder(
5750+
[&](const BoundsAttributedAssignmentGroup &Group) {
5751+
// TODO: Check group constraints.
5752+
},
5753+
[&](const Expr *E, const ValueDecl *VD) {
5754+
diagnoseTooComplexAssignToBoundsAttributed(E, VD, Handler, Ctx);
5755+
});
5756+
Finder.Visit(S);
5757+
}
5758+
54475759
void clang::checkUnsafeBufferUsage(const Decl *D,
54485760
UnsafeBufferUsageHandler &Handler,
5449-
bool EmitSuggestions) {
5761+
bool EmitSuggestions,
5762+
bool BoundsSafetyAttributesEnabled) {
54505763
#ifndef NDEBUG
54515764
Handler.clearDebugNotes();
54525765
#endif
@@ -5492,6 +5805,11 @@ void clang::checkUnsafeBufferUsage(const Decl *D,
54925805
for (Stmt *S : Stmts) {
54935806
findGadgets(S, D->getASTContext(), Handler, EmitSuggestions, FixableGadgets,
54945807
WarningGadgets, Tracker);
5808+
5809+
// Run the bounds-safety assignment analysis if the attributes are enabled,
5810+
// otherwise don't waste cycles.
5811+
if (BoundsSafetyAttributesEnabled)
5812+
checkBoundsSafetyAssignments(S, Handler, D->getASTContext());
54955813
}
54965814
applyGadgets(D, std::move(FixableGadgets), std::move(WarningGadgets),
54975815
std::move(Tracker), Handler, EmitSuggestions);

0 commit comments

Comments
 (0)