Skip to content

Commit 7f73488

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 077208e commit 7f73488

File tree

5 files changed

+272
-1
lines changed

5 files changed

+272
-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
@@ -14273,6 +14273,13 @@ def warn_assign_to_count_attributed_must_be_simple_stmt : Warning<
1427314273
"assignment to %select{count-attributed pointer|dependent count}0 '%1' "
1427414274
"must be a simple statement '%1 = ...'">,
1427514275
InGroup<UnsafeBufferUsage>, DefaultIgnore;
14276+
def warn_cannot_assign_to_immutable_bounds_attributed_object : Warning<
14277+
"cannot assign to %select{variable|parameter|member}0 %1 because %select{"
14278+
"it points to a count-attributed pointer|"
14279+
"it points to a dependent count|"
14280+
"its type depends on an inout dependent count|"
14281+
"it's used as dependent count in an inout count-attributed pointer"
14282+
"}2">, InGroup<UnsafeBufferUsage>, DefaultIgnore;
1427614283
#ifndef NDEBUG
1427714284
// Not a user-facing diagnostic. Useful for debugging false negatives in
1427814285
// -fsafe-buffer-usage-suggestions (i.e. lack of -Wunsafe-buffer-usage fixits).

clang/lib/Analysis/UnsafeBufferUsage.cpp

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

5821+
// Checks if the bounds-attributed group does not assign to implicitly
5822+
// immutable objects (check below which patterns are considered immutable).
5823+
// This function returns false iff at least one assignment modifies
5824+
// bounds-attributed object that should be immutable.
5825+
static bool checkImmutableBoundsAttributedObjects(
5826+
const BoundsAttributedAssignmentGroup &Group,
5827+
UnsafeBufferUsageHandler &Handler, ASTContext &Ctx) {
5828+
bool IsGroupSafe = true;
5829+
5830+
for (size_t I = 0, N = Group.Assignments.size(); I < N; ++I) {
5831+
const BinaryOperator *Assign = Group.Assignments[I];
5832+
const BoundsAttributedObject &Object = Group.AssignedObjects[I];
5833+
5834+
const ValueDecl *VD = Object.Decl;
5835+
QualType Ty = VD->getType();
5836+
int DerefLevel = Object.DerefLevel;
5837+
5838+
using AssignKind = UnsafeBufferUsageHandler::AssignToImmutableObjectKind;
5839+
5840+
// void foo(int *__counted_by(*len) *p, int *len) {
5841+
// p = ...;
5842+
// }
5843+
if (DerefLevel == 0 && Ty->isPointerType() &&
5844+
Ty->getPointeeType()->isBoundsAttributedType()) {
5845+
Handler.handleAssignToImmutableObject(Assign, VD,
5846+
AssignKind::PointerToPointer,
5847+
/*IsRelatedToDecl=*/false, Ctx);
5848+
IsGroupSafe = false;
5849+
}
5850+
5851+
// void foo(int *__counted_by(*len) *p, int *len) {
5852+
// len = ...;
5853+
// }
5854+
if (DerefLevel == 0 && VD->isDependentCountWithDeref()) {
5855+
Handler.handleAssignToImmutableObject(Assign, VD,
5856+
AssignKind::PointerToDependentCount,
5857+
/*IsRelatedToDecl=*/false, Ctx);
5858+
IsGroupSafe = false;
5859+
}
5860+
5861+
// `p` below should be immutable, because updating `p` would not be visible
5862+
// on the call-site to `foo`, but `*len` would, which invalidates the
5863+
// relation between the pointer and its count.
5864+
// void foo(int *__counted_by(*len) p, int *len) {
5865+
// p = ...;
5866+
// }
5867+
if (DerefLevel == 0 && Ty->isCountAttributedTypeDependingOnInoutCount()) {
5868+
Handler.handleAssignToImmutableObject(
5869+
Assign, VD, AssignKind::PointerDependingOnInoutCount,
5870+
/*IsRelatedToDecl=*/false, Ctx);
5871+
IsGroupSafe = false;
5872+
}
5873+
5874+
// Same as above, we cannot update `len`, because it won't be visible on
5875+
// the call-site.
5876+
// void foo(int *__counted_by(len) *p, int *__counted_by(len) q, int len) {
5877+
// len = ...; // bad because of p
5878+
// }
5879+
if (VD->isDependentCountWithoutDeref() &&
5880+
VD->isDependentCountThatIsUsedInInoutPointer()) {
5881+
assert(DerefLevel == 0);
5882+
Handler.handleAssignToImmutableObject(
5883+
Assign, VD, AssignKind::DependentCountUsedInInoutPointer,
5884+
/*IsRelatedToDecl=*/false, Ctx);
5885+
IsGroupSafe = false;
5886+
}
5887+
}
5888+
5889+
return IsGroupSafe;
5890+
}
5891+
5892+
// Checks if the bounds-attributed group is safe. This function returns false
5893+
// iff the assignment group is unsafe and diagnostics have been emitted.
5894+
static bool
5895+
checkBoundsAttributedGroup(const BoundsAttributedAssignmentGroup &Group,
5896+
UnsafeBufferUsageHandler &Handler, ASTContext &Ctx) {
5897+
if (!checkImmutableBoundsAttributedObjects(Group, Handler, Ctx))
5898+
return false;
5899+
5900+
// TODO: Add more checks.
5901+
return true;
5902+
}
5903+
58215904
static void
58225905
diagnoseTooComplexAssignToBoundsAttributed(const Expr *E, const ValueDecl *VD,
58235906
UnsafeBufferUsageHandler &Handler,
@@ -5843,7 +5926,7 @@ static void checkBoundsSafetyAssignments(const Stmt *S,
58435926
ASTContext &Ctx) {
58445927
BoundsAttributedGroupFinder Finder(
58455928
[&](const BoundsAttributedAssignmentGroup &Group) {
5846-
// TODO: Check group constraints.
5929+
checkBoundsAttributedGroup(Group, Handler, Ctx);
58475930
},
58485931
[&](const Expr *E, const ValueDecl *VD) {
58495932
diagnoseTooComplexAssignToBoundsAttributed(E, VD, Handler, Ctx);

clang/lib/Sema/AnalysisBasedWarnings.cpp

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2619,6 +2619,30 @@ 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+
switch (VD->getKind()) {
2625+
case Decl::Var:
2626+
return 0;
2627+
case Decl::ParmVar:
2628+
return 1;
2629+
case Decl::Field:
2630+
return 2;
2631+
default:
2632+
llvm_unreachable("unexpected bounds-attributed decl kind");
2633+
return 0;
2634+
}
2635+
}
2636+
2637+
void handleAssignToImmutableObject(const BinaryOperator *Assign,
2638+
const ValueDecl *VD,
2639+
AssignToImmutableObjectKind Kind,
2640+
bool IsRelatedToDecl,
2641+
ASTContext &Ctx) override {
2642+
S.Diag(Assign->getOperatorLoc(),
2643+
diag::warn_cannot_assign_to_immutable_bounds_attributed_object)
2644+
<< getBoundsAttributedObjectKind(VD) << VD << Kind;
2645+
}
26222646
/* TO_UPSTREAM(BoundsSafety) OFF */
26232647

26242648
void handleUnsafeVariableGroup(const VarDecl *Variable,

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

Lines changed: 142 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,148 @@ 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 bad_inout_span(int *__counted_by(*count) *p, size_t *count, std::span<int> sp) {
80+
*p = sp.data(); // TODO-expected-warning{{unsafe assignment to count-attributed pointer}}
81+
*count = 42;
82+
}
83+
84+
void good_inout_subspan_const(int *__counted_by(*count) *p, size_t *count, std::span<int> sp) {
85+
*p = sp.first(42).data();
86+
*count = 42;
87+
}
88+
89+
void good_inout_subspan_var(int *__counted_by(*count) *p, size_t *count, std::span<int> sp, size_t new_count) {
90+
*p = sp.first(new_count).data();
91+
*count = new_count;
92+
}
93+
94+
void good_inout_subspan_complex(int *__counted_by(*count) *p, size_t *count, std::span<int> sp, size_t i, size_t j) {
95+
*p = sp.first(i + j * 2).data();
96+
*count = i + j * 2;
97+
}
98+
99+
void good_inout_span_if(int *__counted_by(*count) *p, size_t *count, std::span<int> sp) {
100+
if (p && count) {
101+
*p = sp.data();
102+
*count = sp.size();
103+
}
104+
}
105+
106+
void bad_inout_span_if(int *__counted_by(*count) *p, size_t *count, std::span<int> sp, size_t size) {
107+
if (p && count) {
108+
*p = sp.data(); // TODO-expected-warning{{unsafe assignment to count-attributed pointer}}
109+
*count = size;
110+
}
111+
}
112+
113+
class inout_class {
114+
void good_inout_span(int *__counted_by(*count) *p, size_t *count, std::span<int> sp) {
115+
*p = sp.data();
116+
*count = sp.size();
117+
}
118+
119+
void bad_inout_span(int *__counted_by(*count) *p, size_t *count, std::span<int> sp) {
120+
*p = sp.data(); // TODO-expected-warning{{unsafe assignment to count-attributed pointer}}
121+
*count = 42;
122+
}
123+
124+
void good_inout_subspan_const(int *__counted_by(*count) *p, size_t *count, std::span<int> sp) {
125+
*p = sp.first(42).data();
126+
*count = 42;
127+
}
128+
};
129+
130+
// Inout pointer
131+
132+
void bad_inout_ptr_span(int *__counted_by(count) *p, int count, std::span<int> sp) {
133+
*p = sp.data(); // TODO-expected-warning{{unsafe assignment to count-attributed pointer}}
134+
}
135+
136+
void good_inout_ptr_subspan(int *__counted_by(count) *p, size_t count, std::span<int> sp) {
137+
*p = sp.first(count).data();
138+
}
139+
140+
void good_inout_ptr_const_subspan(int *__counted_by(42) *p, std::span<int> sp) {
141+
*p = sp.first(42).data();
142+
}
143+
144+
void good_inout_ptr_multi_subspan(int *__counted_by(a + b) *p, size_t a, size_t b, std::span<int> sp) {
145+
*p = sp.first(a + b).data();
146+
}
147+
148+
class inout_ptr_class {
149+
void bad_inout_ptr_span(int *__counted_by(count) *p, int count, std::span<int> sp) {
150+
*p = sp.data(); // TODO-expected-warning{{unsafe assignment to count-attributed pointer}}
151+
}
152+
153+
void good_inout_ptr_subspan(int *__counted_by(count) *p, size_t count, std::span<int> sp) {
154+
*p = sp.first(count).data();
155+
}
156+
};
157+
158+
// Immutable pointers/dependent values
159+
160+
void immutable_ptr_to_ptr(int *__counted_by(*count) *p, int *count) {
161+
p = nullptr; // expected-warning{{cannot assign to parameter 'p' because it points to a count-attributed pointer}}
162+
*count = 0;
163+
}
164+
165+
void immutable_ptr_to_value(int *__counted_by(*count) *p, int *count) {
166+
*p = nullptr;
167+
count = nullptr; // expected-warning{{cannot assign to parameter 'count' because it points to a dependent count}}
168+
}
169+
170+
void immutable_ptr_with_inout_value(int *__counted_by(*count) p, int *count) {
171+
p = nullptr; // expected-warning{{cannot assign to parameter 'p' because its type depends on an inout dependent count}}
172+
*count = 0;
173+
}
174+
175+
void immutable_ptr_with_inout_value2(int *__counted_by(*count) p, int *__counted_by(*count) *q, int *count) {
176+
p = nullptr; // expected-warning{{cannot assign to parameter 'p' because its type depends on an inout dependent count}}
177+
*q = nullptr;
178+
*count = 0;
179+
}
180+
181+
void immutable_value_with_inout_ptr(int *__counted_by(count) *p, int count) {
182+
*p = nullptr;
183+
count = 0; // expected-warning{{cannot assign to parameter 'count' because it's used as dependent count in an inout count-attributed pointer}}
184+
}
185+
186+
void immutable_value_with_inout_ptr2(int *__counted_by(count) p, int *__counted_by(count) *q, int count) {
187+
p = nullptr;
188+
*q = nullptr;
189+
count = 0; // expected-warning{{cannot assign to parameter 'count' because it's used as dependent count in an inout count-attributed pointer}}
190+
}
191+
192+
class immutable_class {
193+
void immutable_ptr_to_ptr(int *__counted_by(*count) *p, int *count) {
194+
p = nullptr; // expected-warning{{cannot assign to parameter 'p' because it points to a count-attributed pointer}}
195+
*count = 0;
196+
}
197+
198+
void immutable_ptr_to_value(int *__counted_by(*count) *p, int *count) {
199+
*p = nullptr;
200+
count = nullptr; // expected-warning{{cannot assign to parameter 'count' because it points to a dependent count}}
201+
}
202+
203+
void immutable_ptr_with_inout_value(int *__counted_by(*count) p, int *count) {
204+
p = nullptr; // expected-warning{{cannot assign to parameter 'p' because its type depends on an inout dependent count}}
205+
*count = 0;
206+
}
207+
208+
void immutable_value_with_inout_ptr(int *__counted_by(count) *p, int count) {
209+
*p = nullptr;
210+
count = 0; // expected-warning{{cannot assign to parameter 'count' because it's used as dependent count in an inout count-attributed pointer}}
211+
}
212+
};
213+
72214
// Assigns to bounds-attributed that we consider too complex to analyze.
73215

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

0 commit comments

Comments
 (0)