Skip to content

Commit b78957c

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 (cherry picked from commit 0227c6f) Conflicts: clang/include/clang/Basic/DiagnosticSemaKinds.td
1 parent fbb4878 commit b78957c

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
@@ -14110,6 +14110,10 @@ def note_unsafe_count_attributed_pointer_argument_null_to_nonnull : Note<
1411014110
def warn_unsafe_single_pointer_argument : Warning<
1411114111
"unsafe assignment to function parameter of __single pointer type">,
1411214112
InGroup<UnsafeBufferUsage>, DefaultIgnore;
14113+
def warn_assign_to_count_attributed_must_be_simple_stmt : Warning<
14114+
"assignment to %select{count-attributed pointer|dependent count}0 '%1' "
14115+
"must be a simple statement '%1 = ...'">,
14116+
InGroup<UnsafeBufferUsage>, DefaultIgnore;
1411314117
#ifndef NDEBUG
1411414118
// Not a user-facing diagnostic. Useful for debugging false negatives in
1411514119
// -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
@@ -5375,9 +5375,322 @@ static void applyGadgets(const Decl *D, FixableGadgetList FixableGadgets,
53755375
}
53765376
}
53775377

5378+
// Checks if Self and Other are the same member bases. This supports only very
5379+
// simple forms of member bases.
5380+
static bool isSameMemberBase(const Expr *Self, const Expr *Other) {
5381+
for (;;) {
5382+
if (Self == Other)
5383+
return true;
5384+
5385+
const auto *SelfICE = dyn_cast<ImplicitCastExpr>(Self);
5386+
const auto *OtherICE = dyn_cast<ImplicitCastExpr>(Other);
5387+
if (SelfICE && OtherICE && SelfICE->getCastKind() == CK_LValueToRValue &&
5388+
OtherICE->getCastKind() == CK_LValueToRValue) {
5389+
Self = SelfICE->getSubExpr();
5390+
Other = OtherICE->getSubExpr();
5391+
}
5392+
5393+
const auto *SelfDRE = dyn_cast<DeclRefExpr>(Self);
5394+
const auto *OtherDRE = dyn_cast<DeclRefExpr>(Other);
5395+
if (SelfDRE && OtherDRE)
5396+
return SelfDRE->getDecl() == OtherDRE->getDecl();
5397+
5398+
const auto *SelfME = dyn_cast<MemberExpr>(Self);
5399+
const auto *OtherME = dyn_cast<MemberExpr>(Other);
5400+
if (!SelfME || !OtherME ||
5401+
SelfME->getMemberDecl() != OtherME->getMemberDecl()) {
5402+
return false;
5403+
}
5404+
5405+
Self = SelfME->getBase();
5406+
Other = OtherME->getBase();
5407+
}
5408+
}
5409+
5410+
using DependentDeclSetTy = llvm::SmallPtrSet<const ValueDecl *, 4>;
5411+
5412+
// Gets the set/closure of bounds-attributed pointers and counts that belong to
5413+
// the same group. Consider the following example:
5414+
// int a, b, c;
5415+
// int *__counted_by(a + b) p;
5416+
// int *__counted_by(b - c) q;
5417+
// Passing any of the variables above as `InitVD`, the function should return
5418+
// the closure `{a, b, c, p, q}`.
5419+
static DependentDeclSetTy getBoundsAttributedClosure(const ValueDecl *InitVD) {
5420+
DependentDeclSetTy Set;
5421+
5422+
llvm::SmallVector<const ValueDecl *, 4> WorkList;
5423+
WorkList.push_back(InitVD);
5424+
5425+
while (!WorkList.empty()) {
5426+
const ValueDecl *CurVD = WorkList.pop_back_val();
5427+
bool Inserted = Set.insert(CurVD).second;
5428+
if (!Inserted)
5429+
continue;
5430+
5431+
// If CurVD is a dependent decl, add the pointers that depend on CurVD.
5432+
for (const auto *Attr : CurVD->specific_attrs<DependerDeclsAttr>()) {
5433+
for (const Decl *D : Attr->dependerDecls()) {
5434+
if (const auto *VD = dyn_cast<ValueDecl>(D))
5435+
WorkList.push_back(VD);
5436+
}
5437+
}
5438+
5439+
// If CurVD is a bounds-attributed pointer (or pointer to it), add its
5440+
// dependent decls.
5441+
QualType Ty = CurVD->getType();
5442+
const auto *BAT = Ty->getAs<BoundsAttributedType>();
5443+
if (!BAT && Ty->isPointerType())
5444+
BAT = Ty->getPointeeType()->getAs<BoundsAttributedType>();
5445+
if (BAT) {
5446+
for (const auto &DI : BAT->dependent_decls())
5447+
WorkList.push_back(DI.getDecl());
5448+
}
5449+
}
5450+
5451+
return Set;
5452+
}
5453+
5454+
// Bounds-attributed pointer or dependent count.
5455+
struct BoundsAttributedObject {
5456+
const ValueDecl *Decl = nullptr;
5457+
const Expr *MemberBase = nullptr;
5458+
int DerefLevel = 0;
5459+
};
5460+
5461+
static std::optional<BoundsAttributedObject>
5462+
getBoundsAttributedObject(const Expr *E) {
5463+
E = E->IgnoreParenCasts();
5464+
5465+
int DerefLevel = 0;
5466+
while (const auto *UO = dyn_cast<UnaryOperator>(E)) {
5467+
if (UO->getOpcode() == UO_Deref)
5468+
DerefLevel++;
5469+
else if (UO->getOpcode() == UO_AddrOf)
5470+
DerefLevel--;
5471+
else
5472+
break;
5473+
E = UO->getSubExpr()->IgnoreParenCasts();
5474+
}
5475+
assert(DerefLevel >= 0);
5476+
5477+
const ValueDecl *Decl;
5478+
const Expr *MemberBase = nullptr;
5479+
5480+
if (const auto *DRE = dyn_cast<DeclRefExpr>(E))
5481+
Decl = DRE->getDecl();
5482+
else if (const auto *ME = dyn_cast<MemberExpr>(E)) {
5483+
Decl = ME->getMemberDecl();
5484+
MemberBase = ME->getBase();
5485+
} else
5486+
return std::nullopt;
5487+
5488+
QualType Ty = Decl->getType();
5489+
bool IsBoundsAttributedPointer =
5490+
Ty->isBoundsAttributedType() ||
5491+
(Ty->isPointerType() && Ty->getPointeeType()->isBoundsAttributedType());
5492+
if (IsBoundsAttributedPointer || Decl->isDependentCount())
5493+
return {{Decl, MemberBase, DerefLevel}};
5494+
5495+
return std::nullopt;
5496+
}
5497+
5498+
struct BoundsAttributedAssignmentGroup {
5499+
DependentDeclSetTy DeclClosure;
5500+
llvm::SmallVector<const BinaryOperator *, 4> Assignments;
5501+
llvm::SmallVector<BoundsAttributedObject, 4> AssignedObjects;
5502+
using DeclUseTy = std::pair<const ValueDecl *, const Expr *>;
5503+
const Expr *MemberBase = nullptr;
5504+
5505+
void init(const BoundsAttributedObject &Object) {
5506+
DeclClosure = getBoundsAttributedClosure(Object.Decl);
5507+
MemberBase = Object.MemberBase;
5508+
}
5509+
5510+
void clear() {
5511+
DeclClosure.clear();
5512+
Assignments.clear();
5513+
AssignedObjects.clear();
5514+
MemberBase = nullptr;
5515+
}
5516+
5517+
bool empty() const {
5518+
return DeclClosure.empty();
5519+
}
5520+
5521+
bool isPartOfGroup(const BoundsAttributedObject &Object) const {
5522+
if (!DeclClosure.contains(Object.Decl))
5523+
return false;
5524+
if (MemberBase)
5525+
return Object.MemberBase &&
5526+
isSameMemberBase(MemberBase, Object.MemberBase);
5527+
return true;
5528+
}
5529+
5530+
void addAssignment(const BoundsAttributedObject &Object,
5531+
const BinaryOperator *BO) {
5532+
Assignments.push_back(BO);
5533+
AssignedObjects.push_back(Object);
5534+
}
5535+
};
5536+
5537+
// Visitor that is responsible for finding bounds-attributed assignment groups
5538+
// and other assignments that can modify bounds-attributed objects.
5539+
//
5540+
// A bounds-attributed group must be a group of assignments that are children
5541+
// of a CompoundStmt:
5542+
// void foo(int *__counted_by(count) p, int count) {
5543+
// p = ...;
5544+
// count = ...;
5545+
// }
5546+
// Here, the group consists of both assignments to p and count. Note that the
5547+
// function body is a CompoundStmt.
5548+
//
5549+
// We consider assignments to bounds-attributed objects that are not simple
5550+
// statements or not children of a CompoundStmt as too complex, and we give up
5551+
// trying to put them in a bounds-attributed group:
5552+
// void foo(int *__counted_by(count) p, int count) {
5553+
// q = p = ...;
5554+
// ^ too complex assignment to __counted_by() pointer
5555+
// n = count += ...;
5556+
// ^ too complex assignment to dependent count
5557+
// }
5558+
// Note that while those assignments are descendants of a CompoundStmt, they
5559+
// are not its direct children.
5560+
struct BoundsAttributedGroupFinder
5561+
: public ConstStmtVisitor<BoundsAttributedGroupFinder> {
5562+
using GroupHandlerTy = void(const BoundsAttributedAssignmentGroup &Group);
5563+
using AssignHandlerTy = void(const Expr *, const ValueDecl *);
5564+
std::function<GroupHandlerTy> GroupHandler;
5565+
std::function<AssignHandlerTy> TooComplexAssignHandler;
5566+
BoundsAttributedAssignmentGroup CurGroup;
5567+
5568+
explicit BoundsAttributedGroupFinder(
5569+
std::function<GroupHandlerTy> GroupHandler,
5570+
std::function<AssignHandlerTy> TooComplexAssignHandler)
5571+
: GroupHandler(std::move(GroupHandler)),
5572+
TooComplexAssignHandler(std::move(TooComplexAssignHandler)) {}
5573+
5574+
void VisitChildren(const Stmt *S) {
5575+
for (const Stmt *Child : S->children()) {
5576+
if (Child)
5577+
Visit(Child);
5578+
}
5579+
}
5580+
5581+
void VisitStmt(const Stmt *S) { VisitChildren(S); }
5582+
5583+
void VisitCompoundStmt(const CompoundStmt *CS) {
5584+
for (const Stmt *Child : CS->children()) {
5585+
const Stmt *E = Child;
5586+
5587+
// See through `ExprWithCleanups`. Clang will attach those nodes when C++
5588+
// temporary object needs to be materialized. In our case, this can
5589+
// happen when we create a temporary span with `sp.first()`. Then, the
5590+
// structure is going to be:
5591+
// CompoundStmt
5592+
// `-ExprWithCleanups
5593+
// `-BinaryOperator ...
5594+
if (const auto *EWC = dyn_cast<ExprWithCleanups>(E))
5595+
E = EWC->getSubExpr();
5596+
5597+
const auto *BO = dyn_cast<BinaryOperator>(E);
5598+
if (BO && BO->getOpcode() == BO_Assign)
5599+
handleAssignInCompound(BO);
5600+
else {
5601+
finishGroup();
5602+
Visit(Child);
5603+
}
5604+
}
5605+
5606+
finishGroup();
5607+
}
5608+
5609+
void handleAssignInCompound(const BinaryOperator *AssignOp) {
5610+
const auto ObjectOpt = getBoundsAttributedObject(AssignOp->getLHS());
5611+
if (!ObjectOpt.has_value()) {
5612+
finishGroup();
5613+
VisitChildren(AssignOp);
5614+
return;
5615+
}
5616+
5617+
if (!CurGroup.isPartOfGroup(*ObjectOpt)) {
5618+
finishGroup();
5619+
CurGroup.init(*ObjectOpt);
5620+
}
5621+
5622+
CurGroup.addAssignment(*ObjectOpt, AssignOp);
5623+
VisitChildren(AssignOp->getRHS());
5624+
}
5625+
5626+
void finishGroup() {
5627+
if (CurGroup.empty())
5628+
return;
5629+
GroupHandler(CurGroup);
5630+
CurGroup.clear();
5631+
}
5632+
5633+
// Handle statements that might modify bounds-attributed pointer/count, but
5634+
// are not children of a CompoundStmt.
5635+
5636+
void VisitBinaryOperator(const BinaryOperator *BO) {
5637+
VisitChildren(BO);
5638+
5639+
if (BO->isAssignmentOp())
5640+
checkTooComplexAssign(BO, BO->getLHS());
5641+
}
5642+
5643+
void VisitUnaryOperator(const UnaryOperator *UO) {
5644+
VisitChildren(UO);
5645+
5646+
if (UO->isIncrementDecrementOp())
5647+
checkTooComplexAssign(UO, UO->getSubExpr());
5648+
}
5649+
5650+
void checkTooComplexAssign(const Expr *E, const Expr *Sub) {
5651+
const auto DA = getBoundsAttributedObject(Sub);
5652+
if (DA.has_value())
5653+
TooComplexAssignHandler(E, DA->Decl);
5654+
}
5655+
};
5656+
5657+
static void
5658+
diagnoseTooComplexAssignToBoundsAttributed(const Expr *E, const ValueDecl *VD,
5659+
UnsafeBufferUsageHandler &Handler,
5660+
ASTContext &Ctx) {
5661+
// Don't diagnose pointer arithmetic, since -Wunsafe-buffer-usage already does
5662+
// it.
5663+
bool IsPtrArith = false;
5664+
if (E->getType()->isPointerType()) {
5665+
if (const auto *BO = dyn_cast<BinaryOperator>(E))
5666+
IsPtrArith = BO->isCompoundAssignmentOp();
5667+
else if (const auto *UO = dyn_cast<UnaryOperator>(E)) {
5668+
assert(UO->isIncrementDecrementOp());
5669+
IsPtrArith = true;
5670+
}
5671+
}
5672+
if (!IsPtrArith)
5673+
Handler.handleTooComplexCountAttributedAssign(
5674+
E, VD, /*IsRelatedToDecl=*/false, Ctx);
5675+
}
5676+
5677+
static void checkBoundsSafetyAssignments(const Stmt *S,
5678+
UnsafeBufferUsageHandler &Handler,
5679+
ASTContext &Ctx) {
5680+
BoundsAttributedGroupFinder Finder(
5681+
[&](const BoundsAttributedAssignmentGroup &Group) {
5682+
// TODO: Check group constraints.
5683+
},
5684+
[&](const Expr *E, const ValueDecl *VD) {
5685+
diagnoseTooComplexAssignToBoundsAttributed(E, VD, Handler, Ctx);
5686+
});
5687+
Finder.Visit(S);
5688+
}
5689+
53785690
void clang::checkUnsafeBufferUsage(const Decl *D,
53795691
UnsafeBufferUsageHandler &Handler,
5380-
bool EmitSuggestions) {
5692+
bool EmitSuggestions,
5693+
bool BoundsSafetyAttributesEnabled) {
53815694
#ifndef NDEBUG
53825695
Handler.clearDebugNotes();
53835696
#endif
@@ -5423,6 +5736,11 @@ void clang::checkUnsafeBufferUsage(const Decl *D,
54235736
for (Stmt *S : Stmts) {
54245737
findGadgets(S, D->getASTContext(), Handler, EmitSuggestions, FixableGadgets,
54255738
WarningGadgets, Tracker);
5739+
5740+
// Run the bounds-safety assignment analysis if the attributes are enabled,
5741+
// otherwise don't waste cycles.
5742+
if (BoundsSafetyAttributesEnabled)
5743+
checkBoundsSafetyAssignments(S, Handler, D->getASTContext());
54265744
}
54275745
applyGadgets(D, std::move(FixableGadgets), std::move(WarningGadgets),
54285746
std::move(Tracker), Handler, EmitSuggestions);

0 commit comments

Comments
 (0)