Skip to content

Commit 6466fd2

Browse files
[-Wunsafe-buffer-usage] Check count-attributed objects that are used and assigned in the same group
Check if the bounds-attributed group has assignments to objects that are also used in the same group. In those cases, the correctness of the group might depend on the order of assignments. We conservatively disallow such assignments. In the example below, the bounds-check in `sp.first()` uses the value of `b` before the later update, which can lead to OOB if `b` was less than 42. ``` void foo(int *__counted_by(a + b) p, int a, int b, std::span<int> sp) { p = sp.first(b + 42).data(); b = 42; // b is assigned and used a = b; } ``` rdar://161608319 (cherry picked from commit 7e4ad9d)
1 parent 81c6606 commit 6466fd2

File tree

5 files changed

+113
-1
lines changed

5 files changed

+113
-1
lines changed

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

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -180,6 +180,12 @@ class UnsafeBufferUsageHandler {
180180
ASTContext &Ctx) {
181181
handleUnsafeOperation(Assign, IsRelatedToDecl, Ctx);
182182
}
183+
184+
virtual void handleAssignedAndUsed(const BinaryOperator *Assign,
185+
const Expr *Use, const ValueDecl *VD,
186+
bool IsRelatedToDecl, ASTContext &Ctx) {
187+
handleUnsafeOperation(Assign, IsRelatedToDecl, Ctx);
188+
}
183189
/* TO_UPSTREAM(BoundsSafety) OFF */
184190

185191
/// Invoked when a fix is suggested against a variable. This function groups

clang/include/clang/Basic/DiagnosticSemaKinds.td

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14127,6 +14127,9 @@ def warn_missing_assignments_in_bounds_attributed_group : Warning<
1412714127
def warn_duplicated_assignment_in_bounds_attributed_group : Warning<
1412814128
"duplicated assignment to %select{variable|parameter|member}0 %1 in "
1412914129
"bounds-attributed group">, InGroup<UnsafeBufferUsage>, DefaultIgnore;
14130+
def warn_assigned_and_used_in_bounds_attributed_group : Warning<
14131+
"%select{variable|parameter|member}0 %1 is assigned and used in the same "
14132+
"bounds-attributed group">, InGroup<UnsafeBufferUsage>, DefaultIgnore;
1413014133
#ifndef NDEBUG
1413114134
// Not a user-facing diagnostic. Useful for debugging false negatives in
1413214135
// -fsafe-buffer-usage-suggestions (i.e. lack of -Wunsafe-buffer-usage fixits).

clang/lib/Analysis/UnsafeBufferUsage.cpp

Lines changed: 76 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5457,6 +5457,16 @@ struct BoundsAttributedObject {
54575457
const ValueDecl *Decl = nullptr;
54585458
const Expr *MemberBase = nullptr;
54595459
int DerefLevel = 0;
5460+
5461+
bool operator==(const BoundsAttributedObject &Other) const {
5462+
if (Other.Decl != Decl || Other.DerefLevel != DerefLevel)
5463+
return false;
5464+
if (Other.MemberBase == MemberBase)
5465+
return true;
5466+
if (Other.MemberBase == nullptr || MemberBase == nullptr)
5467+
return false;
5468+
return isSameMemberBase(Other.MemberBase, MemberBase);
5469+
}
54605470
};
54615471

54625472
static std::optional<BoundsAttributedObject>
@@ -5500,7 +5510,7 @@ struct BoundsAttributedAssignmentGroup {
55005510
DependentDeclSetTy DeclClosure;
55015511
llvm::SmallVector<const BinaryOperator *, 4> Assignments;
55025512
llvm::SmallVector<BoundsAttributedObject, 4> AssignedObjects;
5503-
using DeclUseTy = std::pair<const ValueDecl *, const Expr *>;
5513+
llvm::SmallDenseMap<const ValueDecl *, const Expr *, 4> Uses;
55045514
const Expr *MemberBase = nullptr;
55055515

55065516
void init(const BoundsAttributedObject &Object) {
@@ -5512,6 +5522,7 @@ struct BoundsAttributedAssignmentGroup {
55125522
DeclClosure.clear();
55135523
Assignments.clear();
55145524
AssignedObjects.clear();
5525+
Uses.clear();
55155526
MemberBase = nullptr;
55165527
}
55175528

@@ -5653,6 +5664,22 @@ struct BoundsAttributedGroupFinder
56535664
if (DA.has_value())
56545665
TooComplexAssignHandler(E, DA->Decl);
56555666
}
5667+
5668+
// Collect uses of decls belonging to the current group by visiting all DREs
5669+
// and MemberExprs.
5670+
5671+
void addUseIfPartOfCurGroup(const Expr *E) {
5672+
const auto DA = getBoundsAttributedObject(E);
5673+
if (DA.has_value() && CurGroup.isPartOfGroup(*DA))
5674+
CurGroup.Uses.insert({DA->Decl, E});
5675+
}
5676+
5677+
void VisitDeclRefExpr(const DeclRefExpr *DRE) { addUseIfPartOfCurGroup(DRE); }
5678+
5679+
void VisitMemberExpr(const MemberExpr *ME) {
5680+
addUseIfPartOfCurGroup(ME);
5681+
Visit(ME->getBase());
5682+
}
56565683
};
56575684

56585685
// Checks if the bounds-attributed group does not assign to implicitly
@@ -5784,6 +5811,52 @@ static bool checkMissingAndDuplicatedAssignments(
57845811
return IsGroupSafe;
57855812
}
57865813

5814+
// Checks if the bounds-attributed group has assignments to objects that are
5815+
// also used in the same group. In those cases, the correctness of the group
5816+
// might depend on the order of assignments. We conservatively disallow such
5817+
// assignments.
5818+
//
5819+
// In the example below, the bounds-check in `sp.first()` uses the value of `b`
5820+
// before the later update, which can lead to OOB if `b` was less than 42.
5821+
// void foo(int *__counted_by(a + b) p, int a, int b, std::span<int> sp) {
5822+
// p = sp.first(b + 42).data();
5823+
// b = 42; // b is assigned and used
5824+
// a = b;
5825+
// }
5826+
static bool checkAssignedAndUsed(const BoundsAttributedAssignmentGroup &Group,
5827+
UnsafeBufferUsageHandler &Handler,
5828+
ASTContext &Ctx) {
5829+
const auto &Uses = Group.Uses;
5830+
if (Uses.empty())
5831+
return true;
5832+
5833+
bool IsGroupSafe = true;
5834+
5835+
for (size_t I = 0, N = Group.AssignedObjects.size(); I < N; ++I) {
5836+
const BoundsAttributedObject &LHSObj = Group.AssignedObjects[I];
5837+
const BinaryOperator *Assign = Group.Assignments[I];
5838+
5839+
// Ignore self assignments, because they don't matter, since the value stays
5840+
// the same.
5841+
const auto RHSObj = getBoundsAttributedObject(Assign->getRHS());
5842+
bool IsSelfAssign = RHSObj.has_value() && *RHSObj == LHSObj;
5843+
if (IsSelfAssign)
5844+
continue;
5845+
5846+
const ValueDecl *VD = LHSObj.Decl;
5847+
const auto It = Uses.find(VD);
5848+
if (It == Uses.end())
5849+
continue;
5850+
5851+
const Expr *Use = It->second;
5852+
Handler.handleAssignedAndUsed(Assign, Use, VD,
5853+
/*IsRelatedToDecl=*/false, Ctx);
5854+
IsGroupSafe = false;
5855+
}
5856+
5857+
return IsGroupSafe;
5858+
}
5859+
57875860
// Checks if the bounds-attributed group is safe. This function returns false
57885861
// iff the assignment group is unsafe and diagnostics have been emitted.
57895862
static bool
@@ -5793,6 +5866,8 @@ checkBoundsAttributedGroup(const BoundsAttributedAssignmentGroup &Group,
57935866
return false;
57945867
if (!checkMissingAndDuplicatedAssignments(Group, Handler, Ctx))
57955868
return false;
5869+
if (!checkAssignedAndUsed(Group, Handler, Ctx))
5870+
return false;
57965871
// TODO: Add more checks.
57975872
return true;
57985873
}

clang/lib/Sema/AnalysisBasedWarnings.cpp

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2675,6 +2675,16 @@ class UnsafeBufferUsageReporter : public UnsafeBufferUsageHandler {
26752675
S.Diag(PrevAssign->getOperatorLoc(),
26762676
diag::note_bounds_safety_previous_assignment);
26772677
}
2678+
2679+
void handleAssignedAndUsed(const BinaryOperator *Assign, const Expr *Use,
2680+
const ValueDecl *VD,
2681+
[[maybe_unused]] bool IsRelatedToDecl,
2682+
[[maybe_unused]] ASTContext &Ctx) override {
2683+
S.Diag(Assign->getOperatorLoc(),
2684+
diag::warn_assigned_and_used_in_bounds_attributed_group)
2685+
<< getBoundsAttributedObjectKind(VD) << VD;
2686+
S.Diag(Use->getBeginLoc(), diag::note_used_here);
2687+
}
26782688
/* TO_UPSTREAM(BoundsSafety) OFF */
26792689

26802690
void handleUnsafeVariableGroup(const VarDecl *Variable,

clang/test/SemaCXX/warn-unsafe-buffer-usage-count-attributed-pointer-assignment.cpp

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -339,6 +339,24 @@ void duplicated_complex(int *__counted_by(a + b) p,
339339
q = nullptr; // expected-warning{{duplicated assignment to parameter 'q' in bounds-attributed group}}
340340
}
341341

342+
// Assigned and used
343+
344+
void good_assigned_and_used(int *__counted_by(count) p, int count, std::span<int> sp) {
345+
p = sp.first(count).data();
346+
count = count;
347+
}
348+
349+
void bad_assigned_and_used(int *__counted_by(count) p, int count, std::span<int> sp, int new_count) {
350+
p = sp.first(count).data(); // expected-note{{used here}}
351+
count = new_count; // expected-warning{{parameter 'count' is assigned and used in the same bounds-attributed group}}
352+
}
353+
354+
void bad_assigned_and_used2(int *__counted_by(a + b) p, int a, int b, std::span<int> sp) {
355+
p = sp.first(b + 42).data(); // expected-note{{used here}}
356+
b = 42; // expected-warning{{parameter 'b' is assigned and used in the same bounds-attributed group}}
357+
a = b;
358+
}
359+
342360
// Assigns to bounds-attributed that we consider too complex to analyze.
343361

344362
void too_complex_assign_to_ptr(int *__counted_by(count) p, int count, int *q) {

0 commit comments

Comments
 (0)