Skip to content

Commit 23ece78

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
1 parent 3147c6e commit 23ece78

File tree

5 files changed

+112
-1
lines changed

5 files changed

+112
-1
lines changed

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

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

186192
/// 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
@@ -14291,6 +14291,9 @@ def warn_missing_assignments_in_bounds_attributed_group : Warning<
1429114291
def warn_duplicated_assignment_in_bounds_attributed_group : Warning<
1429214292
"duplicated assignment to %select{variable|parameter|member}0 %1 in "
1429314293
"bounds-attributed group">, InGroup<UnsafeBufferUsage>, DefaultIgnore;
14294+
def warn_assigned_and_used_in_bounds_attributed_group : Warning<
14295+
"%select{variable|parameter|member}0 %1 is assigned and used in the same "
14296+
"bounds-attributed group">, InGroup<UnsafeBufferUsage>, DefaultIgnore;
1429414297
#ifndef NDEBUG
1429514298
// Not a user-facing diagnostic. Useful for debugging false negatives in
1429614299
// -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
@@ -5620,6 +5620,16 @@ struct BoundsAttributedObject {
56205620
const ValueDecl *Decl = nullptr;
56215621
const Expr *MemberBase = nullptr;
56225622
int DerefLevel = 0;
5623+
5624+
bool operator==(const BoundsAttributedObject &Other) const {
5625+
if (Other.Decl != Decl || Other.DerefLevel != DerefLevel)
5626+
return false;
5627+
if (Other.MemberBase == MemberBase)
5628+
return true;
5629+
if (Other.MemberBase == nullptr || MemberBase == nullptr)
5630+
return false;
5631+
return isSameMemberBase(Other.MemberBase, MemberBase);
5632+
}
56235633
};
56245634

56255635
static std::optional<BoundsAttributedObject>
@@ -5663,7 +5673,7 @@ struct BoundsAttributedAssignmentGroup {
56635673
DependentDeclSetTy DeclClosure;
56645674
llvm::SmallVector<const BinaryOperator *, 4> Assignments;
56655675
llvm::SmallVector<BoundsAttributedObject, 4> AssignedObjects;
5666-
using DeclUseTy = std::pair<const ValueDecl *, const Expr *>;
5676+
llvm::SmallDenseMap<const ValueDecl *, const Expr *, 4> Uses;
56675677
const Expr *MemberBase = nullptr;
56685678

56695679
void init(const BoundsAttributedObject &Object) {
@@ -5675,6 +5685,7 @@ struct BoundsAttributedAssignmentGroup {
56755685
DeclClosure.clear();
56765686
Assignments.clear();
56775687
AssignedObjects.clear();
5688+
Uses.clear();
56785689
MemberBase = nullptr;
56795690
}
56805691

@@ -5816,6 +5827,22 @@ struct BoundsAttributedGroupFinder
58165827
if (DA.has_value())
58175828
TooComplexAssignHandler(E, DA->Decl);
58185829
}
5830+
5831+
// Collect uses of decls belonging to the current group by visiting all DREs
5832+
// and MemberExprs.
5833+
5834+
void addUseIfPartOfCurGroup(const Expr *E) {
5835+
const auto DA = getBoundsAttributedObject(E);
5836+
if (DA.has_value() && CurGroup.isPartOfGroup(*DA))
5837+
CurGroup.Uses.insert({DA->Decl, E});
5838+
}
5839+
5840+
void VisitDeclRefExpr(const DeclRefExpr *DRE) { addUseIfPartOfCurGroup(DRE); }
5841+
5842+
void VisitMemberExpr(const MemberExpr *ME) {
5843+
addUseIfPartOfCurGroup(ME);
5844+
Visit(ME->getBase());
5845+
}
58195846
};
58205847

58215848
// Checks if the bounds-attributed group does not assign to implicitly
@@ -5947,6 +5974,52 @@ static bool checkMissingAndDuplicatedAssignments(
59475974
return IsGroupSafe;
59485975
}
59495976

5977+
// Checks if the bounds-attributed group has assignments to objects that are
5978+
// also used in the same group. In those cases, the correctness of the group
5979+
// might depend on the order of assignments. We conservatively disallow such
5980+
// assignments.
5981+
//
5982+
// In the example below, the bounds-check in `sp.first()` uses the value of `b`
5983+
// before the later update, which can lead to OOB if `b` was less than 42.
5984+
// void foo(int *__counted_by(a + b) p, int a, int b, std::span<int> sp) {
5985+
// p = sp.first(b + 42).data();
5986+
// b = 42; // b is assigned and used
5987+
// a = b;
5988+
// }
5989+
static bool checkAssignedAndUsed(const BoundsAttributedAssignmentGroup &Group,
5990+
UnsafeBufferUsageHandler &Handler,
5991+
ASTContext &Ctx) {
5992+
const auto &Uses = Group.Uses;
5993+
if (Uses.empty())
5994+
return true;
5995+
5996+
bool IsGroupSafe = true;
5997+
5998+
for (size_t I = 0, N = Group.AssignedObjects.size(); I < N; ++I) {
5999+
const BoundsAttributedObject &LHSObj = Group.AssignedObjects[I];
6000+
const BinaryOperator *Assign = Group.Assignments[I];
6001+
6002+
// Ignore self assignments, because they don't matter, since the value stays
6003+
// the same.
6004+
const auto RHSObj = getBoundsAttributedObject(Assign->getRHS());
6005+
bool IsSelfAssign = RHSObj.has_value() && *RHSObj == LHSObj;
6006+
if (IsSelfAssign)
6007+
continue;
6008+
6009+
const ValueDecl *VD = LHSObj.Decl;
6010+
const auto It = Uses.find(VD);
6011+
if (It == Uses.end())
6012+
continue;
6013+
6014+
const Expr *Use = It->second;
6015+
Handler.handleAssignedAndUsed(Assign, Use, VD,
6016+
/*IsRelatedToDecl=*/false, Ctx);
6017+
IsGroupSafe = false;
6018+
}
6019+
6020+
return IsGroupSafe;
6021+
}
6022+
59506023
// Checks if the bounds-attributed group is safe. This function returns false
59516024
// iff the assignment group is unsafe and diagnostics have been emitted.
59526025
static bool
@@ -5956,6 +6029,8 @@ checkBoundsAttributedGroup(const BoundsAttributedAssignmentGroup &Group,
59566029
return false;
59576030
if (!checkMissingAndDuplicatedAssignments(Group, Handler, Ctx))
59586031
return false;
6032+
if (!checkAssignedAndUsed(Group, Handler, Ctx))
6033+
return false;
59596034
// TODO: Add more checks.
59606035
return true;
59616036
}

clang/lib/Sema/AnalysisBasedWarnings.cpp

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2685,6 +2685,15 @@ class UnsafeBufferUsageReporter : public UnsafeBufferUsageHandler {
26852685
S.Diag(PrevAssign->getOperatorLoc(),
26862686
diag::note_bounds_safety_previous_assignment);
26872687
}
2688+
2689+
void handleAssignedAndUsed(const BinaryOperator *Assign, const Expr *Use,
2690+
const ValueDecl *VD, bool IsRelatedToDecl,
2691+
ASTContext &Ctx) override {
2692+
S.Diag(Assign->getOperatorLoc(),
2693+
diag::warn_assigned_and_used_in_bounds_attributed_group)
2694+
<< getBoundsAttributedObjectKind(VD) << VD;
2695+
S.Diag(Use->getBeginLoc(), diag::note_used_here);
2696+
}
26882697
/* TO_UPSTREAM(BoundsSafety) OFF */
26892698

26902699
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)