Skip to content

Commit 3e72540

Browse files
[-Wunsafe-buffer-usage] Check standalone 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 standalone assignments to bounds-attributed objects (pointers and dependent counts). As a PoC, this commit adds checks for standalone 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 0797b32 commit 3e72540

File tree

5 files changed

+406
-3
lines changed

5 files changed

+406
-3
lines changed

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

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

147152
/// Invoked when a fix is suggested against a variable. This function groups
@@ -196,7 +201,8 @@ class UnsafeBufferUsageHandler {
196201
// This function invokes the analysis and allows the caller to react to it
197202
// through the handler class.
198203
void checkUnsafeBufferUsage(const Decl *D, UnsafeBufferUsageHandler &Handler,
199-
bool EmitSuggestions);
204+
bool EmitSuggestions,
205+
bool BoundsSafetyAttributes = false);
200206

201207
namespace internal {
202208
// 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
@@ -14250,6 +14250,10 @@ def note_unsafe_count_attributed_pointer_argument_null_to_nonnull : Note<
1425014250
def warn_unsafe_single_pointer_argument : Warning<
1425114251
"unsafe assignment to function parameter of __single pointer type">,
1425214252
InGroup<UnsafeBufferUsage>, DefaultIgnore;
14253+
def warn_standalone_assign_to_bounds_attributed_object : Warning<
14254+
"assignment to %select{bounds-attributed pointer|dependent value}0 %1 "
14255+
"must be inside of a bounds-attributed group in a compound statement">,
14256+
InGroup<UnsafeBufferUsage>, DefaultIgnore;
1425314257
#ifndef NDEBUG
1425414258
// Not a user-facing diagnostic. Useful for debugging false negatives in
1425514259
// -fsafe-buffer-usage-suggestions (i.e. lack of -Wunsafe-buffer-usage fixits).

clang/lib/Analysis/UnsafeBufferUsage.cpp

Lines changed: 313 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5433,9 +5433,316 @@ static void applyGadgets(const Decl *D, FixableGadgetList FixableGadgets,
54335433
}
54345434
}
54355435

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

clang/lib/Sema/AnalysisBasedWarnings.cpp

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2609,6 +2609,17 @@ class UnsafeBufferUsageReporter : public UnsafeBufferUsageHandler {
26092609
ASTContext &Ctx) override {
26102610
S.Diag(Arg->getBeginLoc(), diag::warn_unsafe_single_pointer_argument);
26112611
}
2612+
2613+
void handleStandaloneAssign(const Expr *E, const ValueDecl *VD,
2614+
bool IsRelatedToDecl, ASTContext &Ctx) override {
2615+
SourceLocation Loc = E->getBeginLoc();
2616+
if (const auto *BO = dyn_cast<BinaryOperator>(E))
2617+
Loc = BO->getOperatorLoc();
2618+
2619+
S.Diag(Loc, diag::warn_standalone_assign_to_bounds_attributed_object)
2620+
<< VD->hasAttr<DependerDeclsAttr>() << VD;
2621+
// TODO: << VD->isDependentValue() << VD;
2622+
}
26122623
/* TO_UPSTREAM(BoundsSafety) OFF */
26132624

26142625
void handleUnsafeVariableGroup(const VarDecl *Variable,
@@ -3455,6 +3466,7 @@ void clang::sema::AnalysisBasedWarnings::IssueWarnings(
34553466
bool UnsafeBufferUsageShouldSuggestSuggestions =
34563467
UnsafeBufferUsageCanEmitSuggestions &&
34573468
!DiagOpts.ShowSafeBufferUsageSuggestions;
3469+
bool BoundsSafetyAttributes = S.getLangOpts().BoundsSafetyAttributes;
34583470
UnsafeBufferUsageReporter R(S, UnsafeBufferUsageShouldSuggestSuggestions);
34593471

34603472
// The Callback function that performs analyses:
@@ -3472,7 +3484,8 @@ void clang::sema::AnalysisBasedWarnings::IssueWarnings(
34723484
!Diags.isIgnored(diag::warn_unsafe_buffer_libc_call,
34733485
Node->getBeginLoc())) {
34743486
clang::checkUnsafeBufferUsage(Node, R,
3475-
UnsafeBufferUsageShouldEmitSuggestions);
3487+
UnsafeBufferUsageShouldEmitSuggestions,
3488+
BoundsSafetyAttributes);
34763489
}
34773490

34783491
// More analysis ...

0 commit comments

Comments
 (0)