Skip to content

Commit 739ea51

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 5b5708e commit 739ea51

File tree

5 files changed

+118
-0
lines changed

5 files changed

+118
-0
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
@@ -14289,6 +14289,9 @@ def warn_missing_assignments_in_bounds_attributed_group : Warning<
1428914289
def warn_duplicated_assignment_in_bounds_attributed_group : Warning<
1429014290
"duplicated assignment to %select{variable|parameter|member}0 %1 in "
1429114291
"bounds-attributed group">, InGroup<UnsafeBufferUsage>, DefaultIgnore;
14292+
def warn_assigned_and_used_in_bounds_attributed_group : Warning<
14293+
"%select{variable|parameter|member}0 %1 is assigned and used in the same "
14294+
"bounds-attributed group">, InGroup<UnsafeBufferUsage>, DefaultIgnore;
1429214295
#ifndef NDEBUG
1429314296
// Not a user-facing diagnostic. Useful for debugging false negatives in
1429414297
// -fsafe-buffer-usage-suggestions (i.e. lack of -Wunsafe-buffer-usage fixits).

clang/lib/Analysis/UnsafeBufferUsage.cpp

Lines changed: 82 additions & 0 deletions
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>
@@ -5664,6 +5674,7 @@ struct BoundsAttributedAssignmentGroup {
56645674
llvm::SmallVector<const BinaryOperator *, 4> Assignments;
56655675
llvm::SmallVector<BoundsAttributedObject, 4> AssignedObjects;
56665676
using DeclUseTy = std::pair<const ValueDecl *, const Expr *>;
5677+
llvm::SmallVector<DeclUseTy, 4> Uses;
56675678
const Expr *MemberBase = nullptr;
56685679

56695680
void init(const BoundsAttributedObject &Object) {
@@ -5675,6 +5686,7 @@ struct BoundsAttributedAssignmentGroup {
56755686
DeclClosure.clear();
56765687
Assignments.clear();
56775688
AssignedObjects.clear();
5689+
Uses.clear();
56785690
MemberBase = nullptr;
56795691
}
56805692

@@ -5696,6 +5708,10 @@ struct BoundsAttributedAssignmentGroup {
56965708
Assignments.push_back(BO);
56975709
AssignedObjects.push_back(Object);
56985710
}
5711+
5712+
void addUse(const BoundsAttributedObject &Object, const Expr *E) {
5713+
Uses.emplace_back(Object.Decl, E);
5714+
}
56995715
};
57005716

57015717
// Visitor that is responsible for finding bounds-attributed assignment groups
@@ -5816,6 +5832,21 @@ struct BoundsAttributedGroupFinder
58165832
if (DA.has_value())
58175833
TooComplexAssignHandler(E, DA->Decl);
58185834
}
5835+
5836+
// Collect uses.
5837+
5838+
void addUseIfPartOfCurGroup(const Expr *E) {
5839+
const auto DA = getBoundsAttributedObject(E);
5840+
if (DA.has_value() && CurGroup.isPartOfGroup(*DA))
5841+
CurGroup.addUse(*DA, E);
5842+
}
5843+
5844+
void VisitDeclRefExpr(const DeclRefExpr *DRE) { addUseIfPartOfCurGroup(DRE); }
5845+
5846+
void VisitMemberExpr(const MemberExpr *ME) {
5847+
addUseIfPartOfCurGroup(ME);
5848+
Visit(ME->getBase());
5849+
}
58195850
};
58205851

58215852
// Checks if the bounds-attributed group does not assign to implicitly
@@ -5947,6 +5978,55 @@ static bool checkMissingAndDuplicatedAssignments(
59475978
return IsGroupSafe;
59485979
}
59495980

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

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 2{{parameter 'b' is assigned and used in the same bounds-attributed group}}
357+
a = b; // expected-note{{used here}}
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)