Skip to content

Commit 0e187f0

Browse files
Merge pull request #9126 from swiftlang/msamak-cherrypick-attribut
[attributes][-Wunsafe-buffer-usage] Support adding unsafe_buffer_usage attribute to struct fields (llvm#101585)
2 parents 14b4e6a + 1b39233 commit 0e187f0

File tree

7 files changed

+297
-63
lines changed

7 files changed

+297
-63
lines changed

clang/include/clang/Basic/Attr.td

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4480,7 +4480,7 @@ def ReleaseHandle : InheritableParamAttr {
44804480

44814481
def UnsafeBufferUsage : InheritableAttr {
44824482
let Spellings = [Clang<"unsafe_buffer_usage">];
4483-
let Subjects = SubjectList<[Function]>;
4483+
let Subjects = SubjectList<[Function, Field]>;
44844484
let Documentation = [UnsafeBufferUsageDocs];
44854485
}
44864486

clang/include/clang/Basic/AttrDocs.td

Lines changed: 81 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -6933,77 +6933,103 @@ def UnsafeBufferUsageDocs : Documentation {
69336933
let Category = DocCatFunction;
69346934
let Content = [{
69356935
The attribute ``[[clang::unsafe_buffer_usage]]`` should be placed on functions
6936-
that need to be avoided as they are prone to buffer overflows. It is designed to
6937-
work together with the off-by-default compiler warning ``-Wunsafe-buffer-usage``
6938-
to help codebases transition away from raw pointer based buffer management,
6939-
in favor of safer abstractions such as C++20 ``std::span``. The attribute causes
6940-
``-Wunsafe-buffer-usage`` to warn on every use of the function, and it may
6941-
enable ``-Wunsafe-buffer-usage`` to emit automatic fix-it hints
6942-
which would help the user replace such unsafe functions with safe
6936+
that need to be avoided as they are prone to buffer overflows or unsafe buffer
6937+
struct fields. It is designed to work together with the off-by-default compiler
6938+
warning ``-Wunsafe-buffer-usage``to help codebases transition away from raw pointer
6939+
based buffer management, in favor of safer abstractions such as C++20 ``std::span``.
6940+
The attribute causes ``-Wunsafe-buffer-usage`` to warn on every use of the function or
6941+
the field it is attached to, and it may also lead to emission of automatic fix-it
6942+
hints which would help the user replace the use of unsafe functions(/fields) with safe
69436943
alternatives, though the attribute can be used even when the fix can't be automated.
69446944

6945-
The attribute does not suppress ``-Wunsafe-buffer-usage`` inside the function
6946-
to which it is attached. These warnings still need to be addressed.
6945+
* Attribute attached to functions: The attribute does not suppress
6946+
``-Wunsafe-buffer-usage`` inside the function to which it is attached.
6947+
These warnings still need to be addressed.
69476948

6948-
The attribute is warranted even if the only way a function can overflow
6949-
the buffer is by violating the function's preconditions. For example, it
6950-
would make sense to put the attribute on function ``foo()`` below because
6951-
passing an incorrect size parameter would cause a buffer overflow:
6949+
The attribute is warranted even if the only way a function can overflow
6950+
the buffer is by violating the function's preconditions. For example, it
6951+
would make sense to put the attribute on function ``foo()`` below because
6952+
passing an incorrect size parameter would cause a buffer overflow:
69526953

6953-
.. code-block:: c++
6954+
.. code-block:: c++
69546955

6955-
[[clang::unsafe_buffer_usage]]
6956-
void foo(int *buf, size_t size) {
6957-
for (size_t i = 0; i < size; ++i) {
6958-
buf[i] = i;
6956+
[[clang::unsafe_buffer_usage]]
6957+
void foo(int *buf, size_t size) {
6958+
for (size_t i = 0; i < size; ++i) {
6959+
buf[i] = i;
6960+
}
69596961
}
6960-
}
69616962

6962-
The attribute is NOT warranted when the function uses safe abstractions,
6963-
assuming that these abstractions weren't misused outside the function.
6964-
For example, function ``bar()`` below doesn't need the attribute,
6965-
because assuming that the container ``buf`` is well-formed (has size that
6966-
fits the original buffer it refers to), overflow cannot occur:
6963+
The attribute is NOT warranted when the function uses safe abstractions,
6964+
assuming that these abstractions weren't misused outside the function.
6965+
For example, function ``bar()`` below doesn't need the attribute,
6966+
because assuming that the container ``buf`` is well-formed (has size that
6967+
fits the original buffer it refers to), overflow cannot occur:
69676968

6968-
.. code-block:: c++
6969+
.. code-block:: c++
69696970

6970-
void bar(std::span<int> buf) {
6971-
for (size_t i = 0; i < buf.size(); ++i) {
6972-
buf[i] = i;
6971+
void bar(std::span<int> buf) {
6972+
for (size_t i = 0; i < buf.size(); ++i) {
6973+
buf[i] = i;
6974+
}
69736975
}
6974-
}
69756976

6976-
In this case function ``bar()`` enables the user to keep the buffer
6977-
"containerized" in a span for as long as possible. On the other hand,
6978-
Function ``foo()`` in the previous example may have internal
6979-
consistency, but by accepting a raw buffer it requires the user to unwrap
6980-
their span, which is undesirable according to the programming model
6981-
behind ``-Wunsafe-buffer-usage``.
6977+
In this case function ``bar()`` enables the user to keep the buffer
6978+
"containerized" in a span for as long as possible. On the other hand,
6979+
Function ``foo()`` in the previous example may have internal
6980+
consistency, but by accepting a raw buffer it requires the user to unwrap
6981+
their span, which is undesirable according to the programming model
6982+
behind ``-Wunsafe-buffer-usage``.
69826983

6983-
The attribute is warranted when a function accepts a raw buffer only to
6984-
immediately put it into a span:
6984+
The attribute is warranted when a function accepts a raw buffer only to
6985+
immediately put it into a span:
69856986

6986-
.. code-block:: c++
6987+
.. code-block:: c++
69876988

6988-
[[clang::unsafe_buffer_usage]]
6989-
void baz(int *buf, size_t size) {
6990-
std::span<int> sp{ buf, size };
6991-
for (size_t i = 0; i < sp.size(); ++i) {
6992-
sp[i] = i;
6989+
[[clang::unsafe_buffer_usage]]
6990+
void baz(int *buf, size_t size) {
6991+
std::span<int> sp{ buf, size };
6992+
for (size_t i = 0; i < sp.size(); ++i) {
6993+
sp[i] = i;
6994+
}
69936995
}
6994-
}
69956996

6996-
In this case ``baz()`` does not contain any unsafe operations, but the awkward
6997-
parameter type causes the caller to unwrap the span unnecessarily.
6998-
Note that regardless of the attribute, code inside ``baz()`` isn't flagged
6999-
by ``-Wunsafe-buffer-usage`` as unsafe. It is definitely undesirable,
7000-
but if ``baz()`` is on an API surface, there is no way to improve it
7001-
to make it as safe as ``bar()`` without breaking the source and binary
7002-
compatibility with existing users of the function. In such cases
7003-
the proper solution would be to create a different function (possibly
7004-
an overload of ``baz()``) that accepts a safe container like ``bar()``,
7005-
and then use the attribute on the original ``baz()`` to help the users
7006-
update their code to use the new function.
6997+
In this case ``baz()`` does not contain any unsafe operations, but the awkward
6998+
parameter type causes the caller to unwrap the span unnecessarily.
6999+
Note that regardless of the attribute, code inside ``baz()`` isn't flagged
7000+
by ``-Wunsafe-buffer-usage`` as unsafe. It is definitely undesirable,
7001+
but if ``baz()`` is on an API surface, there is no way to improve it
7002+
to make it as safe as ``bar()`` without breaking the source and binary
7003+
compatibility with existing users of the function. In such cases
7004+
the proper solution would be to create a different function (possibly
7005+
an overload of ``baz()``) that accepts a safe container like ``bar()``,
7006+
and then use the attribute on the original ``baz()`` to help the users
7007+
update their code to use the new function.
7008+
7009+
* Attribute attached to fields: The attribute should only be attached to
7010+
struct fields, if the fields can not be updated to a safe type with bounds
7011+
check, such as std::span. In other words, the buffers prone to unsafe accesses
7012+
should always be updated to use safe containers/views and attaching the attribute
7013+
must be last resort when such an update is infeasible.
7014+
7015+
The attribute can be placed on individual fields or a set of them as shown below.
7016+
7017+
.. code-block:: c++
7018+
7019+
struct A {
7020+
[[clang::unsafe_buffer_usage]]
7021+
int *ptr1;
7022+
7023+
[[clang::unsafe_buffer_usage]]
7024+
int *ptr2, buf[10];
7025+
7026+
[[clang::unsafe_buffer_usage]]
7027+
size_t sz;
7028+
};
7029+
7030+
Here, every read/write to the fields ptr1, ptr2, buf and sz will trigger a warning
7031+
that the field has been explcitly marked as unsafe due to unsafe-buffer operations.
7032+
70077033
}];
70087034
}
70097035

clang/include/clang/Basic/DiagnosticSemaKinds.td

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12426,7 +12426,8 @@ def warn_unsafe_buffer_variable : Warning<
1242612426
InGroup<UnsafeBufferUsage>, DefaultIgnore;
1242712427
def warn_unsafe_buffer_operation : Warning<
1242812428
"%select{unsafe pointer operation|unsafe pointer arithmetic|"
12429-
"unsafe buffer access|function introduces unsafe buffer manipulation|unsafe invocation of span::data}0">,
12429+
"unsafe buffer access|function introduces unsafe buffer manipulation|unsafe invocation of span::data|"
12430+
"field %1 prone to unsafe buffer manipulation}0">,
1243012431
InGroup<UnsafeBufferUsage>, DefaultIgnore;
1243112432
def note_unsafe_buffer_operation : Note<
1243212433
"used%select{| in pointer arithmetic| in buffer access}0 here">;

clang/lib/Analysis/UnsafeBufferUsage.cpp

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -926,22 +926,27 @@ class CArrayToPtrAssignmentGadget : public FixableGadget {
926926
/// A call of a function or method that performs unchecked buffer operations
927927
/// over one of its pointer parameters.
928928
class UnsafeBufferUsageAttrGadget : public WarningGadget {
929-
constexpr static const char *const OpTag = "call_expr";
930-
const CallExpr *Op;
929+
constexpr static const char *const OpTag = "attr_expr";
930+
const Expr *Op;
931931

932932
public:
933933
UnsafeBufferUsageAttrGadget(const MatchFinder::MatchResult &Result)
934934
: WarningGadget(Kind::UnsafeBufferUsageAttr),
935-
Op(Result.Nodes.getNodeAs<CallExpr>(OpTag)) {}
935+
Op(Result.Nodes.getNodeAs<Expr>(OpTag)) {}
936936

937937
static bool classof(const Gadget *G) {
938938
return G->getKind() == Kind::UnsafeBufferUsageAttr;
939939
}
940940

941941
static Matcher matcher() {
942+
auto HasUnsafeFieldDecl =
943+
member(fieldDecl(hasAttr(attr::UnsafeBufferUsage)));
944+
942945
auto HasUnsafeFnDecl =
943946
callee(functionDecl(hasAttr(attr::UnsafeBufferUsage)));
944-
return stmt(callExpr(HasUnsafeFnDecl).bind(OpTag));
947+
948+
return stmt(anyOf(callExpr(HasUnsafeFnDecl).bind(OpTag),
949+
memberExpr(HasUnsafeFieldDecl).bind(OpTag)));
945950
}
946951

947952
void handleUnsafeOperation(UnsafeBufferUsageHandler &Handler,

clang/lib/Sema/AnalysisBasedWarnings.cpp

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2231,6 +2231,7 @@ class UnsafeBufferUsageReporter : public UnsafeBufferUsageHandler {
22312231
SourceLocation Loc;
22322232
SourceRange Range;
22332233
unsigned MsgParam = 0;
2234+
NamedDecl *D = nullptr;
22342235
if (const auto *ASE = dyn_cast<ArraySubscriptExpr>(Operation)) {
22352236
Loc = ASE->getBase()->getExprLoc();
22362237
Range = ASE->getBase()->getSourceRange();
@@ -2261,6 +2262,12 @@ class UnsafeBufferUsageReporter : public UnsafeBufferUsageHandler {
22612262
// note_unsafe_buffer_operation doesn't have this mode yet.
22622263
assert(!IsRelatedToDecl && "Not implemented yet!");
22632264
MsgParam = 3;
2265+
} else if (isa<MemberExpr>(Operation)) {
2266+
// note_unsafe_buffer_operation doesn't have this mode yet.
2267+
assert(!IsRelatedToDecl && "Not implemented yet!");
2268+
auto ME = dyn_cast<MemberExpr>(Operation);
2269+
D = ME->getMemberDecl();
2270+
MsgParam = 5;
22642271
} else if (const auto *ECE = dyn_cast<ExplicitCastExpr>(Operation)) {
22652272
QualType destType = ECE->getType();
22662273
if (!isa<PointerType>(destType))
@@ -2285,7 +2292,12 @@ class UnsafeBufferUsageReporter : public UnsafeBufferUsageHandler {
22852292
"Variables blamed for unsafe buffer usage without suggestions!");
22862293
S.Diag(Loc, diag::note_unsafe_buffer_operation) << MsgParam << Range;
22872294
} else {
2288-
S.Diag(Loc, diag::warn_unsafe_buffer_operation) << MsgParam << Range;
2295+
if (D) {
2296+
S.Diag(Loc, diag::warn_unsafe_buffer_operation)
2297+
<< MsgParam << D << Range;
2298+
} else {
2299+
S.Diag(Loc, diag::warn_unsafe_buffer_operation) << MsgParam << Range;
2300+
}
22892301
if (SuggestSuggestions) {
22902302
S.Diag(Loc, diag::note_safe_buffer_usage_suggestions_disabled);
22912303
}

clang/test/Misc/pragma-attribute-supported-attributes-list.test

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -203,7 +203,7 @@
203203
// CHECK-NEXT: TransparentStepping (SubjectMatchRule_function)
204204
// CHECK-NEXT: TrivialABI (SubjectMatchRule_record)
205205
// CHECK-NEXT: Uninitialized (SubjectMatchRule_variable_is_local)
206-
// CHECK-NEXT: UnsafeBufferUsage (SubjectMatchRule_function)
206+
// CHECK-NEXT: UnsafeBufferUsage (SubjectMatchRule_function, SubjectMatchRule_field)
207207
// CHECK-NEXT: UseHandle (SubjectMatchRule_variable_is_parameter)
208208
// CHECK-NEXT: VTablePointerAuthentication (SubjectMatchRule_record)
209209
// CHECK-NEXT: VecReturn (SubjectMatchRule_record)

0 commit comments

Comments
 (0)