Skip to content

Commit ed31544

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 cb79f5b commit ed31544

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
@@ -143,6 +143,13 @@ class UnsafeBufferUsageHandler {
143143
ASTContext &Ctx) {
144144
handleUnsafeOperation(Arg, IsRelatedToDecl, Ctx);
145145
}
146+
147+
virtual void handleTooComplexCountAttributedAssign(const Expr *E,
148+
const ValueDecl *VD,
149+
bool IsRelatedToDecl,
150+
ASTContext &Ctx) {
151+
handleUnsafeOperation(E, IsRelatedToDecl, Ctx);
152+
}
146153
/* TO_UPSTREAM(BoundsSafety) OFF */
147154

148155
/// Invoked when a fix is suggested against a variable. This function groups
@@ -203,7 +210,8 @@ class UnsafeBufferUsageHandler {
203210
// This function invokes the analysis and allows the caller to react to it
204211
// through the handler class.
205212
void checkUnsafeBufferUsage(const Decl *D, UnsafeBufferUsageHandler &Handler,
206-
bool EmitSuggestions);
213+
bool EmitSuggestions,
214+
bool BoundsSafetyAttributesEnabled = false);
207215

208216
namespace internal {
209217
// 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
@@ -14260,6 +14260,10 @@ def warn_unsafe_single_pointer_argument : Warning<
1426014260
InGroup<UnsafeBufferUsage>, DefaultIgnore;
1426114261
def warn_unsafe_buffer_usage_unique_ptr_array_access : Warning<"direct access using operator[] on std::unique_ptr<T[]> is unsafe due to lack of bounds checking">,
1426214262
InGroup<UnsafeBufferUsageInUniquePtrArrayAccess>, DefaultIgnore;
14263+
def warn_assign_to_count_attributed_must_be_simple_stmt : Warning<
14264+
"assignment to %select{count-attributed pointer|dependent count}0 '%1' "
14265+
"must be a simple statement '%1 = ...'">,
14266+
InGroup<UnsafeBufferUsage>, DefaultIgnore;
1426314267
#ifndef NDEBUG
1426414268
// Not a user-facing diagnostic. Useful for debugging false negatives in
1426514269
// -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
@@ -5539,9 +5539,322 @@ static void applyGadgets(const Decl *D, FixableGadgetList FixableGadgets,
55395539
}
55405540
}
55415541

5542+
// Checks if Self and Other are the same member bases. This supports only very
5543+
// simple forms of member bases.
5544+
static bool isSameMemberBase(const Expr *Self, const Expr *Other) {
5545+
for (;;) {
5546+
if (Self == Other)
5547+
return true;
5548+
5549+
const auto *SelfICE = dyn_cast<ImplicitCastExpr>(Self);
5550+
const auto *OtherICE = dyn_cast<ImplicitCastExpr>(Other);
5551+
if (SelfICE && OtherICE && SelfICE->getCastKind() == CK_LValueToRValue &&
5552+
OtherICE->getCastKind() == CK_LValueToRValue) {
5553+
Self = SelfICE->getSubExpr();
5554+
Other = OtherICE->getSubExpr();
5555+
}
5556+
5557+
const auto *SelfDRE = dyn_cast<DeclRefExpr>(Self);
5558+
const auto *OtherDRE = dyn_cast<DeclRefExpr>(Other);
5559+
if (SelfDRE && OtherDRE)
5560+
return SelfDRE->getDecl() == OtherDRE->getDecl();
5561+
5562+
const auto *SelfME = dyn_cast<MemberExpr>(Self);
5563+
const auto *OtherME = dyn_cast<MemberExpr>(Other);
5564+
if (!SelfME || !OtherME ||
5565+
SelfME->getMemberDecl() != OtherME->getMemberDecl()) {
5566+
return false;
5567+
}
5568+
5569+
Self = SelfME->getBase();
5570+
Other = OtherME->getBase();
5571+
}
5572+
}
5573+
5574+
using DependentDeclSetTy = llvm::SmallPtrSet<const ValueDecl *, 4>;
5575+
5576+
// Gets the set/closure of bounds-attributed pointers and counts that belong to
5577+
// the same group. Consider the following example:
5578+
// int a, b, c;
5579+
// int *__counted_by(a + b) p;
5580+
// int *__counted_by(b - c) q;
5581+
// Passing any of the variables above as `InitVD`, the function should return
5582+
// the closure `{a, b, c, p, q}`.
5583+
static DependentDeclSetTy getBoundsAttributedClosure(const ValueDecl *InitVD) {
5584+
DependentDeclSetTy Set;
5585+
5586+
llvm::SmallVector<const ValueDecl *, 4> WorkList;
5587+
WorkList.push_back(InitVD);
5588+
5589+
while (!WorkList.empty()) {
5590+
const ValueDecl *CurVD = WorkList.pop_back_val();
5591+
bool Inserted = Set.insert(CurVD).second;
5592+
if (!Inserted)
5593+
continue;
5594+
5595+
// If CurVD is a dependent decl, add the pointers that depend on CurVD.
5596+
for (const auto *Attr : CurVD->specific_attrs<DependerDeclsAttr>()) {
5597+
for (const Decl *D : Attr->dependerDecls()) {
5598+
if (const auto *VD = dyn_cast<ValueDecl>(D))
5599+
WorkList.push_back(VD);
5600+
}
5601+
}
5602+
5603+
// If CurVD is a bounds-attributed pointer (or pointer to it), add its
5604+
// dependent decls.
5605+
QualType Ty = CurVD->getType();
5606+
const auto *BAT = Ty->getAs<BoundsAttributedType>();
5607+
if (!BAT && Ty->isPointerType())
5608+
BAT = Ty->getPointeeType()->getAs<BoundsAttributedType>();
5609+
if (BAT) {
5610+
for (const auto &DI : BAT->dependent_decls())
5611+
WorkList.push_back(DI.getDecl());
5612+
}
5613+
}
5614+
5615+
return Set;
5616+
}
5617+
5618+
// Bounds-attributed pointer or dependent count.
5619+
struct BoundsAttributedObject {
5620+
const ValueDecl *Decl = nullptr;
5621+
const Expr *MemberBase = nullptr;
5622+
int DerefLevel = 0;
5623+
};
5624+
5625+
static std::optional<BoundsAttributedObject>
5626+
getBoundsAttributedObject(const Expr *E) {
5627+
E = E->IgnoreParenCasts();
5628+
5629+
int DerefLevel = 0;
5630+
while (const auto *UO = dyn_cast<UnaryOperator>(E)) {
5631+
if (UO->getOpcode() == UO_Deref)
5632+
DerefLevel++;
5633+
else if (UO->getOpcode() == UO_AddrOf)
5634+
DerefLevel--;
5635+
else
5636+
break;
5637+
E = UO->getSubExpr()->IgnoreParenCasts();
5638+
}
5639+
assert(DerefLevel >= 0);
5640+
5641+
const ValueDecl *Decl;
5642+
const Expr *MemberBase = nullptr;
5643+
5644+
if (const auto *DRE = dyn_cast<DeclRefExpr>(E))
5645+
Decl = DRE->getDecl();
5646+
else if (const auto *ME = dyn_cast<MemberExpr>(E)) {
5647+
Decl = ME->getMemberDecl();
5648+
MemberBase = ME->getBase();
5649+
} else
5650+
return std::nullopt;
5651+
5652+
QualType Ty = Decl->getType();
5653+
bool IsBoundsAttributedPointer =
5654+
Ty->isBoundsAttributedType() ||
5655+
(Ty->isPointerType() && Ty->getPointeeType()->isBoundsAttributedType());
5656+
if (IsBoundsAttributedPointer || Decl->isDependentCount())
5657+
return {{Decl, MemberBase, DerefLevel}};
5658+
5659+
return std::nullopt;
5660+
}
5661+
5662+
struct BoundsAttributedAssignmentGroup {
5663+
DependentDeclSetTy DeclClosure;
5664+
llvm::SmallVector<const BinaryOperator *, 4> Assignments;
5665+
llvm::SmallVector<BoundsAttributedObject, 4> AssignedObjects;
5666+
using DeclUseTy = std::pair<const ValueDecl *, const Expr *>;
5667+
const Expr *MemberBase = nullptr;
5668+
5669+
void init(const BoundsAttributedObject &Object) {
5670+
DeclClosure = getBoundsAttributedClosure(Object.Decl);
5671+
MemberBase = Object.MemberBase;
5672+
}
5673+
5674+
void clear() {
5675+
DeclClosure.clear();
5676+
Assignments.clear();
5677+
AssignedObjects.clear();
5678+
MemberBase = nullptr;
5679+
}
5680+
5681+
bool empty() const {
5682+
return DeclClosure.empty();
5683+
}
5684+
5685+
bool isPartOfGroup(const BoundsAttributedObject &Object) const {
5686+
if (!DeclClosure.contains(Object.Decl))
5687+
return false;
5688+
if (MemberBase)
5689+
return Object.MemberBase &&
5690+
isSameMemberBase(MemberBase, Object.MemberBase);
5691+
return true;
5692+
}
5693+
5694+
void addAssignment(const BoundsAttributedObject &Object,
5695+
const BinaryOperator *BO) {
5696+
Assignments.push_back(BO);
5697+
AssignedObjects.push_back(Object);
5698+
}
5699+
};
5700+
5701+
// Visitor that is responsible for finding bounds-attributed assignment groups
5702+
// and other assignments that can modify bounds-attributed objects.
5703+
//
5704+
// A bounds-attributed group must be a group of assignments that are children
5705+
// of a CompoundStmt:
5706+
// void foo(int *__counted_by(count) p, int count) {
5707+
// p = ...;
5708+
// count = ...;
5709+
// }
5710+
// Here, the group consists of both assignments to p and count. Note that the
5711+
// function body is a CompoundStmt.
5712+
//
5713+
// We consider assignments to bounds-attributed objects that are not simple
5714+
// statements or not children of a CompoundStmt as too complex, and we give up
5715+
// trying to put them in a bounds-attributed group:
5716+
// void foo(int *__counted_by(count) p, int count) {
5717+
// q = p = ...;
5718+
// ^ too complex assignment to __counted_by() pointer
5719+
// n = count += ...;
5720+
// ^ too complex assignment to dependent count
5721+
// }
5722+
// Note that while those assignments are descendants of a CompoundStmt, they
5723+
// are not its direct children.
5724+
struct BoundsAttributedGroupFinder
5725+
: public ConstStmtVisitor<BoundsAttributedGroupFinder> {
5726+
using GroupHandlerTy = void(const BoundsAttributedAssignmentGroup &Group);
5727+
using AssignHandlerTy = void(const Expr *, const ValueDecl *);
5728+
std::function<GroupHandlerTy> GroupHandler;
5729+
std::function<AssignHandlerTy> TooComplexAssignHandler;
5730+
BoundsAttributedAssignmentGroup CurGroup;
5731+
5732+
explicit BoundsAttributedGroupFinder(
5733+
std::function<GroupHandlerTy> GroupHandler,
5734+
std::function<AssignHandlerTy> TooComplexAssignHandler)
5735+
: GroupHandler(std::move(GroupHandler)),
5736+
TooComplexAssignHandler(std::move(TooComplexAssignHandler)) {}
5737+
5738+
void VisitChildren(const Stmt *S) {
5739+
for (const Stmt *Child : S->children()) {
5740+
if (Child)
5741+
Visit(Child);
5742+
}
5743+
}
5744+
5745+
void VisitStmt(const Stmt *S) { VisitChildren(S); }
5746+
5747+
void VisitCompoundStmt(const CompoundStmt *CS) {
5748+
for (const Stmt *Child : CS->children()) {
5749+
const Stmt *E = Child;
5750+
5751+
// See through `ExprWithCleanups`. Clang will attach those nodes when C++
5752+
// temporary object needs to be materialized. In our case, this can
5753+
// happen when we create a temporary span with `sp.first()`. Then, the
5754+
// structure is going to be:
5755+
// CompoundStmt
5756+
// `-ExprWithCleanups
5757+
// `-BinaryOperator ...
5758+
if (const auto *EWC = dyn_cast<ExprWithCleanups>(E))
5759+
E = EWC->getSubExpr();
5760+
5761+
const auto *BO = dyn_cast<BinaryOperator>(E);
5762+
if (BO && BO->getOpcode() == BO_Assign)
5763+
handleAssignInCompound(BO);
5764+
else {
5765+
finishGroup();
5766+
Visit(Child);
5767+
}
5768+
}
5769+
5770+
finishGroup();
5771+
}
5772+
5773+
void handleAssignInCompound(const BinaryOperator *AssignOp) {
5774+
const auto ObjectOpt = getBoundsAttributedObject(AssignOp->getLHS());
5775+
if (!ObjectOpt.has_value()) {
5776+
finishGroup();
5777+
VisitChildren(AssignOp);
5778+
return;
5779+
}
5780+
5781+
if (!CurGroup.isPartOfGroup(*ObjectOpt)) {
5782+
finishGroup();
5783+
CurGroup.init(*ObjectOpt);
5784+
}
5785+
5786+
CurGroup.addAssignment(*ObjectOpt, AssignOp);
5787+
VisitChildren(AssignOp->getRHS());
5788+
}
5789+
5790+
void finishGroup() {
5791+
if (CurGroup.empty())
5792+
return;
5793+
GroupHandler(CurGroup);
5794+
CurGroup.clear();
5795+
}
5796+
5797+
// Handle statements that might modify bounds-attributed pointer/count, but
5798+
// are not children of a CompoundStmt.
5799+
5800+
void VisitBinaryOperator(const BinaryOperator *BO) {
5801+
VisitChildren(BO);
5802+
5803+
if (BO->isAssignmentOp())
5804+
checkTooComplexAssign(BO, BO->getLHS());
5805+
}
5806+
5807+
void VisitUnaryOperator(const UnaryOperator *UO) {
5808+
VisitChildren(UO);
5809+
5810+
if (UO->isIncrementDecrementOp())
5811+
checkTooComplexAssign(UO, UO->getSubExpr());
5812+
}
5813+
5814+
void checkTooComplexAssign(const Expr *E, const Expr *Sub) {
5815+
const auto DA = getBoundsAttributedObject(Sub);
5816+
if (DA.has_value())
5817+
TooComplexAssignHandler(E, DA->Decl);
5818+
}
5819+
};
5820+
5821+
static void
5822+
diagnoseTooComplexAssignToBoundsAttributed(const Expr *E, const ValueDecl *VD,
5823+
UnsafeBufferUsageHandler &Handler,
5824+
ASTContext &Ctx) {
5825+
// Don't diagnose pointer arithmetic, since -Wunsafe-buffer-usage already does
5826+
// it.
5827+
bool IsPtrArith = false;
5828+
if (E->getType()->isPointerType()) {
5829+
if (const auto *BO = dyn_cast<BinaryOperator>(E))
5830+
IsPtrArith = BO->isCompoundAssignmentOp();
5831+
else if (const auto *UO = dyn_cast<UnaryOperator>(E)) {
5832+
assert(UO->isIncrementDecrementOp());
5833+
IsPtrArith = true;
5834+
}
5835+
}
5836+
if (!IsPtrArith)
5837+
Handler.handleTooComplexCountAttributedAssign(
5838+
E, VD, /*IsRelatedToDecl=*/false, Ctx);
5839+
}
5840+
5841+
static void checkBoundsSafetyAssignments(const Stmt *S,
5842+
UnsafeBufferUsageHandler &Handler,
5843+
ASTContext &Ctx) {
5844+
BoundsAttributedGroupFinder Finder(
5845+
[&](const BoundsAttributedAssignmentGroup &Group) {
5846+
// TODO: Check group constraints.
5847+
},
5848+
[&](const Expr *E, const ValueDecl *VD) {
5849+
diagnoseTooComplexAssignToBoundsAttributed(E, VD, Handler, Ctx);
5850+
});
5851+
Finder.Visit(S);
5852+
}
5853+
55425854
void clang::checkUnsafeBufferUsage(const Decl *D,
55435855
UnsafeBufferUsageHandler &Handler,
5544-
bool EmitSuggestions) {
5856+
bool EmitSuggestions,
5857+
bool BoundsSafetyAttributesEnabled) {
55455858
#ifndef NDEBUG
55465859
Handler.clearDebugNotes();
55475860
#endif
@@ -5587,6 +5900,11 @@ void clang::checkUnsafeBufferUsage(const Decl *D,
55875900
for (Stmt *S : Stmts) {
55885901
findGadgets(S, D->getASTContext(), Handler, EmitSuggestions, FixableGadgets,
55895902
WarningGadgets, Tracker);
5903+
5904+
// Run the bounds-safety assignment analysis if the attributes are enabled,
5905+
// otherwise don't waste cycles.
5906+
if (BoundsSafetyAttributesEnabled)
5907+
checkBoundsSafetyAssignments(S, Handler, D->getASTContext());
55905908
}
55915909
applyGadgets(D, std::move(FixableGadgets), std::move(WarningGadgets),
55925910
std::move(Tracker), Handler, EmitSuggestions);

0 commit comments

Comments
 (0)