Skip to content

Commit 1c8ac19

Browse files
[-Wunsafe-buffer-usage] Check assignments to implicitly immutable count-attributed objects
Check assignments to count-attributed objects that are implicitly immutable. For example, assigning to a dependent count that is used in an inout pointer is not allowed, since the update won't be visible on the call-site: ``` void foo(int *__counted_by(count) *out_p, int count) { *out_p = ...; count = ...; // immutable } ``` rdar://161608157
1 parent 07c64d7 commit 1c8ac19

File tree

5 files changed

+206
-1
lines changed

5 files changed

+206
-1
lines changed

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

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -150,6 +150,21 @@ class UnsafeBufferUsageHandler {
150150
ASTContext &Ctx) {
151151
handleUnsafeOperation(E, IsRelatedToDecl, Ctx);
152152
}
153+
154+
enum class AssignToImmutableObjectKind {
155+
PointerToPointer,
156+
PointerToDependentCount,
157+
PointerDependingOnInoutCount,
158+
DependentCountUsedInInoutPointer,
159+
};
160+
161+
virtual void handleAssignToImmutableObject(const BinaryOperator *Assign,
162+
const ValueDecl *VD,
163+
AssignToImmutableObjectKind Kind,
164+
bool IsRelatedToDecl,
165+
ASTContext &Ctx) {
166+
handleUnsafeOperation(Assign, IsRelatedToDecl, Ctx);
167+
}
153168
/* TO_UPSTREAM(BoundsSafety) OFF */
154169

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

clang/include/clang/Basic/DiagnosticSemaKinds.td

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14267,6 +14267,13 @@ def warn_assign_to_count_attributed_must_be_simple_stmt : Warning<
1426714267
"assignment to %select{count-attributed pointer|dependent count}0 '%1' "
1426814268
"must be a simple statement '%1 = ...'">,
1426914269
InGroup<UnsafeBufferUsage>, DefaultIgnore;
14270+
def warn_cannot_assign_to_immutable_bounds_attributed_object : Warning<
14271+
"cannot assign to %select{variable|parameter|member}0 %1 because %select{"
14272+
"it points to a count-attributed pointer|"
14273+
"it points to a dependent count|"
14274+
"its type depends on an inout dependent count|"
14275+
"it's used as dependent count in an inout count-attributed pointer"
14276+
"}2">, InGroup<UnsafeBufferUsage>, DefaultIgnore;
1427014277
#ifndef NDEBUG
1427114278
// Not a user-facing diagnostic. Useful for debugging false negatives in
1427214279
// -fsafe-buffer-usage-suggestions (i.e. lack of -Wunsafe-buffer-usage fixits).

clang/lib/Analysis/UnsafeBufferUsage.cpp

Lines changed: 78 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5818,6 +5818,83 @@ struct BoundsAttributedGroupFinder
58185818
}
58195819
};
58205820

5821+
static bool checkImmutableBoundsAttributedObjects(
5822+
const BoundsAttributedAssignmentGroup &Group,
5823+
UnsafeBufferUsageHandler &Handler, ASTContext &Ctx) {
5824+
bool Ok = true;
5825+
5826+
for (size_t I = 0, N = Group.Assignments.size(); I < N; ++I) {
5827+
const BinaryOperator *Assign = Group.Assignments[I];
5828+
const BoundsAttributedObject &Object = Group.AssignedObjects[I];
5829+
5830+
const ValueDecl *VD = Object.Decl;
5831+
QualType Ty = VD->getType();
5832+
int DerefLevel = Object.DerefLevel;
5833+
5834+
using AssignKind = UnsafeBufferUsageHandler::AssignToImmutableObjectKind;
5835+
5836+
// void foo(int *__counted_by(*len) *p, int *len) {
5837+
// p = ...;
5838+
// }
5839+
if (DerefLevel == 0 && Ty->isPointerType() &&
5840+
Ty->getPointeeType()->isBoundsAttributedType()) {
5841+
Handler.handleAssignToImmutableObject(Assign, VD,
5842+
AssignKind::PointerToPointer,
5843+
/*IsRelatedToDecl=*/false, Ctx);
5844+
Ok = false;
5845+
}
5846+
5847+
// void foo(int *__counted_by(*len) *p, int *len) {
5848+
// len = ...;
5849+
// }
5850+
if (DerefLevel == 0 && VD->isDependentCountWithDeref()) {
5851+
Handler.handleAssignToImmutableObject(Assign, VD,
5852+
AssignKind::PointerToDependentCount,
5853+
/*IsRelatedToDecl=*/false, Ctx);
5854+
Ok = false;
5855+
}
5856+
5857+
// `p` below should be immutable, because updating `p` would not be visible
5858+
// on the call-site to `foo`, but `*len` would, which invalidates the
5859+
// relation between the pointer and its count.
5860+
// void foo(int *__counted_by(*len) p, int *len) {
5861+
// p = ...;
5862+
// }
5863+
if (DerefLevel == 0 && Ty->isCountAttributedTypeDependingOnInoutCount()) {
5864+
Handler.handleAssignToImmutableObject(
5865+
Assign, VD, AssignKind::PointerDependingOnInoutCount,
5866+
/*IsRelatedToDecl=*/false, Ctx);
5867+
Ok = false;
5868+
}
5869+
5870+
// Same as above, we cannot update `len`, because it won't be visible on
5871+
// the call-site.
5872+
// void foo(int *__counted_by(len) *p, int *__counted_by(len) q, int len) {
5873+
// len = ...; // bad because of p
5874+
// }
5875+
if (VD->isDependentCountWithoutDeref() &&
5876+
VD->isDependentCountThatIsUsedInInoutPointer()) {
5877+
assert(DerefLevel == 0);
5878+
Handler.handleAssignToImmutableObject(
5879+
Assign, VD, AssignKind::DependentCountUsedInInoutPointer,
5880+
/*IsRelatedToDecl=*/false, Ctx);
5881+
Ok = false;
5882+
}
5883+
}
5884+
5885+
return Ok;
5886+
}
5887+
5888+
static bool
5889+
checkBoundsAttributedGroup(const BoundsAttributedAssignmentGroup &Group,
5890+
UnsafeBufferUsageHandler &Handler, ASTContext &Ctx) {
5891+
if (!checkImmutableBoundsAttributedObjects(Group, Handler, Ctx))
5892+
return false;
5893+
5894+
// TODO: Add more checks.
5895+
return true;
5896+
}
5897+
58215898
static void
58225899
diagnoseTooComplexAssignToBoundsAttributed(const Expr *E, const ValueDecl *VD,
58235900
UnsafeBufferUsageHandler &Handler,
@@ -5843,7 +5920,7 @@ static void checkBoundsSafetyAssignments(const Stmt *S,
58435920
ASTContext &Ctx) {
58445921
BoundsAttributedGroupFinder Finder(
58455922
[&](const BoundsAttributedAssignmentGroup &Group) {
5846-
// TODO: Check group constraints.
5923+
checkBoundsAttributedGroup(Group, Handler, Ctx);
58475924
},
58485925
[&](const Expr *E, const ValueDecl *VD) {
58495926
diagnoseTooComplexAssignToBoundsAttributed(E, VD, Handler, Ctx);

clang/lib/Sema/AnalysisBasedWarnings.cpp

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2619,6 +2619,24 @@ class UnsafeBufferUsageReporter : public UnsafeBufferUsageHandler {
26192619
S.Diag(Loc, diag::warn_assign_to_count_attributed_must_be_simple_stmt)
26202620
<< VD->isDependentCount() << VD->getName();
26212621
}
2622+
2623+
static int getBoundsAttributedObjectKind(const ValueDecl *VD) {
2624+
if (isa<ParmVarDecl>(VD))
2625+
return 1;
2626+
if (isa<FieldDecl>(VD))
2627+
return 2;
2628+
return 0; // variable
2629+
}
2630+
2631+
void handleAssignToImmutableObject(const BinaryOperator *Assign,
2632+
const ValueDecl *VD,
2633+
AssignToImmutableObjectKind Kind,
2634+
bool IsRelatedToDecl,
2635+
ASTContext &Ctx) override {
2636+
S.Diag(Assign->getOperatorLoc(),
2637+
diag::warn_cannot_assign_to_immutable_bounds_attributed_object)
2638+
<< getBoundsAttributedObjectKind(VD) << VD << Kind;
2639+
}
26222640
/* TO_UPSTREAM(BoundsSafety) OFF */
26232641

26242642
void handleUnsafeVariableGroup(const VarDecl *Variable,

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

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,94 @@ void good_struct_self_loop(cb *c) {
6969
}
7070
}
7171

72+
// Inout pointer and count
73+
74+
void good_inout_span(int *__counted_by(*count) *p, size_t *count, std::span<int> sp) {
75+
*p = sp.data();
76+
*count = sp.size();
77+
}
78+
79+
void good_inout_subspan_const(int *__counted_by(*count) *p, size_t *count, std::span<int> sp) {
80+
*p = sp.first(42).data();
81+
*count = 42;
82+
}
83+
84+
void good_inout_subspan_var(int *__counted_by(*count) *p, size_t *count, std::span<int> sp, size_t new_count) {
85+
*p = sp.first(new_count).data();
86+
*count = new_count;
87+
}
88+
89+
void good_inout_subspan_complex(int *__counted_by(*count) *p, size_t *count, std::span<int> sp, size_t i, size_t j) {
90+
*p = sp.first(i + j * 2).data();
91+
*count = i + j * 2;
92+
}
93+
94+
void good_inout_span_if(int *__counted_by(*count) *p, size_t *count, std::span<int> sp) {
95+
if (p && count) {
96+
*p = sp.data();
97+
*count = sp.size();
98+
}
99+
}
100+
101+
void bad_inout_span_if(int *__counted_by(*count) *p, size_t *count, std::span<int> sp, size_t size) {
102+
if (p && count) {
103+
*p = sp.data(); // TODO-expected-warning{{unsafe assignment to count-attributed pointer}}
104+
*count = size;
105+
}
106+
}
107+
108+
// Inout pointer
109+
110+
void bad_inout_ptr_span(int *__counted_by(count) *p, int count, std::span<int> sp) {
111+
*p = sp.data(); // TODO-expected-warning{{unsafe assignment to count-attributed pointer}}
112+
}
113+
114+
void good_inout_ptr_subspan(int *__counted_by(count) *p, size_t count, std::span<int> sp) {
115+
*p = sp.first(count).data();
116+
}
117+
118+
void good_inout_ptr_const_subspan(int *__counted_by(42) *p, std::span<int> sp) {
119+
*p = sp.first(42).data();
120+
}
121+
122+
void good_inout_ptr_multi_subspan(int *__counted_by(a + b) *p, size_t a, size_t b, std::span<int> sp) {
123+
*p = sp.first(a + b).data();
124+
}
125+
126+
// Immutable pointers/dependent values
127+
128+
void immutable_ptr_to_ptr(int *__counted_by(*count) *p, int *count) {
129+
p = nullptr; // expected-warning{{cannot assign to parameter 'p' because it points to a count-attributed pointer}}
130+
*count = 0;
131+
}
132+
133+
void immutable_ptr_to_value(int *__counted_by(*count) *p, int *count) {
134+
*p = nullptr;
135+
count = nullptr; // expected-warning{{cannot assign to parameter 'count' because it points to a dependent count}}
136+
}
137+
138+
void immutable_ptr_with_inout_value(int *__counted_by(*count) p, int *count) {
139+
p = nullptr; // expected-warning{{cannot assign to parameter 'p' because its type depends on an inout dependent count}}
140+
*count = 0;
141+
}
142+
143+
void immutable_ptr_with_inout_value2(int *__counted_by(*count) p, int *__counted_by(*count) *q, int *count) {
144+
p = nullptr; // expected-warning{{cannot assign to parameter 'p' because its type depends on an inout dependent count}}
145+
*q = nullptr;
146+
*count = 0;
147+
}
148+
149+
void immutable_value_with_inout_ptr(int *__counted_by(count) *p, int count) {
150+
*p = nullptr;
151+
count = 0; // expected-warning{{cannot assign to parameter 'count' because it's used as dependent count in an inout count-attributed pointer}}
152+
}
153+
154+
void immutable_value_with_inout_ptr2(int *__counted_by(count) p, int *__counted_by(count) *q, int count) {
155+
p = nullptr;
156+
*q = nullptr;
157+
count = 0; // expected-warning{{cannot assign to parameter 'count' because it's used as dependent count in an inout count-attributed pointer}}
158+
}
159+
72160
// Assigns to bounds-attributed that we consider too complex to analyze.
73161

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

0 commit comments

Comments
 (0)