Skip to content

Commit 70254e2

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 70254e2

File tree

5 files changed

+399
-3
lines changed

5 files changed

+399
-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 BoundsSafetyAttributes = 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: 315 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5444,9 +5444,318 @@ 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 to bounds-attributed objects that we consider too
5608+
// complex to analyze.
5609+
//
5610+
// Bounds-attributed groups must be inside 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 that are not simple statements inside of a
5619+
// CompoundStmt and modify bounds-attributed objects as too complex, and we give
5620+
// up verifying them:
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+
struct BoundsAttributedGroupFinder
5628+
: public ConstStmtVisitor<BoundsAttributedGroupFinder> {
5629+
using GroupHandlerTy = void(const BoundsAttributedAssignmentGroup &Group);
5630+
using AssignHandlerTy = void(const Expr *, const ValueDecl *);
5631+
std::function<GroupHandlerTy> GroupHandler;
5632+
std::function<AssignHandlerTy> TooComplexAssignHandler;
5633+
BoundsAttributedAssignmentGroup CurGroup;
5634+
5635+
explicit BoundsAttributedGroupFinder(
5636+
std::function<GroupHandlerTy> GroupHandler,
5637+
std::function<AssignHandlerTy> TooComplexAssignHandler)
5638+
: GroupHandler(std::move(GroupHandler)),
5639+
TooComplexAssignHandler(std::move(TooComplexAssignHandler)) {}
5640+
5641+
void VisitChildren(const Stmt *S) {
5642+
for (const Stmt *Child : S->children())
5643+
Visit(Child);
5644+
}
5645+
5646+
void VisitStmt(const Stmt *S) { VisitChildren(S); }
5647+
5648+
void VisitCompoundStmt(const CompoundStmt *CS) {
5649+
for (const Stmt *Child : CS->children()) {
5650+
const Stmt *E = Child;
5651+
5652+
// See through `ExprWithCleanups`. Clang will attach those nodes when C++
5653+
// temporary object needs to be materialized. In our case, this can
5654+
// happen when we create a temporary span with `sp.first()`. Then, the
5655+
// structure is going to be:
5656+
// CompoundStmt
5657+
// `-ExprWithCleanups
5658+
// `-BinaryOperator ...
5659+
if (const auto *EWC = dyn_cast<ExprWithCleanups>(E))
5660+
E = EWC->getSubExpr();
5661+
5662+
const auto *BO = dyn_cast<BinaryOperator>(E);
5663+
if (BO && BO->getOpcode() == BO_Assign)
5664+
HandleAssignInCompound(BO);
5665+
else {
5666+
FinishGroup();
5667+
Visit(Child);
5668+
}
5669+
}
5670+
5671+
FinishGroup();
5672+
}
5673+
5674+
void HandleAssignInCompound(const BinaryOperator *AssignOp) {
5675+
const auto ObjectOpt = getBoundsAttributedObject(AssignOp->getLHS());
5676+
if (!ObjectOpt.has_value()) {
5677+
FinishGroup();
5678+
VisitChildren(AssignOp);
5679+
return;
5680+
}
5681+
5682+
if (!CurGroup.IsPartOfGroup(*ObjectOpt)) {
5683+
FinishGroup();
5684+
CurGroup.Init(*ObjectOpt);
5685+
}
5686+
5687+
CurGroup.AddAssignment(*ObjectOpt, AssignOp);
5688+
VisitChildren(AssignOp->getRHS());
5689+
}
5690+
5691+
void FinishGroup() {
5692+
if (CurGroup.Empty())
5693+
return;
5694+
GroupHandler(CurGroup);
5695+
CurGroup.Clear();
5696+
}
5697+
5698+
// Handle statements that might modify bounds-attributed pointer/count, but
5699+
// are not directly in a CompoundStmt.
5700+
5701+
void VisitBinaryOperator(const BinaryOperator *BO) {
5702+
VisitChildren(BO);
5703+
5704+
if (BO->isAssignmentOp())
5705+
CheckTooComplexAssign(BO, BO->getLHS());
5706+
}
5707+
5708+
void VisitUnaryOperator(const UnaryOperator *UO) {
5709+
VisitChildren(UO);
5710+
5711+
if (UO->isIncrementDecrementOp())
5712+
CheckTooComplexAssign(UO, UO->getSubExpr());
5713+
}
5714+
5715+
void CheckTooComplexAssign(const Expr *E, const Expr *Sub) {
5716+
const auto DA = getBoundsAttributedObject(Sub);
5717+
if (DA.has_value())
5718+
TooComplexAssignHandler(E, DA->Decl);
5719+
}
5720+
};
5721+
5722+
static void
5723+
diagnoseTooComplexAssignToBoundsAttributed(const Expr *E, const ValueDecl *VD,
5724+
UnsafeBufferUsageHandler &Handler,
5725+
ASTContext &Ctx) {
5726+
// Don't diagnose pointer arithmetic, since -Wunsafe-buffer-usage already does
5727+
// it.
5728+
bool IsPtrArith = false;
5729+
if (E->getType()->isPointerType()) {
5730+
if (const auto *BO = dyn_cast<BinaryOperator>(E))
5731+
IsPtrArith = BO->isCompoundAssignmentOp();
5732+
else if (const auto *UO = dyn_cast<UnaryOperator>(E)) {
5733+
assert(UO->isIncrementDecrementOp());
5734+
IsPtrArith = true;
5735+
}
5736+
}
5737+
if (!IsPtrArith)
5738+
Handler.handleTooComplexCountAttributedAssign(
5739+
E, VD, /*IsRelatedToDecl=*/false, Ctx);
5740+
}
5741+
5742+
static void checkBoundsSafetyAssignments(const Stmt *S,
5743+
UnsafeBufferUsageHandler &Handler,
5744+
ASTContext &Ctx) {
5745+
BoundsAttributedGroupFinder Finder(
5746+
[&](const BoundsAttributedAssignmentGroup &Group) {
5747+
// TODO: Check group constraints.
5748+
},
5749+
[&](const Expr *E, const ValueDecl *VD) {
5750+
diagnoseTooComplexAssignToBoundsAttributed(E, VD, Handler, Ctx);
5751+
});
5752+
Finder.Visit(S);
5753+
}
5754+
54475755
void clang::checkUnsafeBufferUsage(const Decl *D,
54485756
UnsafeBufferUsageHandler &Handler,
5449-
bool EmitSuggestions) {
5757+
bool EmitSuggestions,
5758+
bool BoundsSafetyAttributes) {
54505759
#ifndef NDEBUG
54515760
Handler.clearDebugNotes();
54525761
#endif
@@ -5492,6 +5801,11 @@ void clang::checkUnsafeBufferUsage(const Decl *D,
54925801
for (Stmt *S : Stmts) {
54935802
findGadgets(S, D->getASTContext(), Handler, EmitSuggestions, FixableGadgets,
54945803
WarningGadgets, Tracker);
5804+
5805+
// Run the bounds-safety assignment analysis if the attributes are enabled,
5806+
// otherwise don't waste cycles.
5807+
if (BoundsSafetyAttributes)
5808+
checkBoundsSafetyAssignments(S, Handler, D->getASTContext());
54955809
}
54965810
applyGadgets(D, std::move(FixableGadgets), std::move(WarningGadgets),
54975811
std::move(Tracker), Handler, EmitSuggestions);

0 commit comments

Comments
 (0)