Skip to content

Commit 6b652f6

Browse files
malavikasamakMalavikaSamak
andauthored
[attributes][-Wunsafe-buffer-usage] Support adding unsafe_buffer_usage attribute to struct fields (llvm#101585)
Extend the unsafe_buffer_usage attribute, so they can also be added to struct fields. This will cause the compiler to warn about the unsafe field at their access sites. Co-authored-by: MalavikaSamak <[email protected]>
1 parent 715c303 commit 6b652f6

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
@@ -4467,7 +4467,7 @@ def ReleaseHandle : InheritableParamAttr {
44674467

44684468
def UnsafeBufferUsage : InheritableAttr {
44694469
let Spellings = [Clang<"unsafe_buffer_usage">];
4470-
let Subjects = SubjectList<[Function]>;
4470+
let Subjects = SubjectList<[Function, Field]>;
44714471
let Documentation = [UnsafeBufferUsageDocs];
44724472
}
44734473

clang/include/clang/Basic/AttrDocs.td

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

6814-
The attribute does not suppress ``-Wunsafe-buffer-usage`` inside the function
6815-
to which it is attached. These warnings still need to be addressed.
6814+
* Attribute attached to functions: The attribute does not suppress
6815+
``-Wunsafe-buffer-usage`` inside the function to which it is attached.
6816+
These warnings still need to be addressed.
68166817

6817-
The attribute is warranted even if the only way a function can overflow
6818-
the buffer is by violating the function's preconditions. For example, it
6819-
would make sense to put the attribute on function ``foo()`` below because
6820-
passing an incorrect size parameter would cause a buffer overflow:
6818+
The attribute is warranted even if the only way a function can overflow
6819+
the buffer is by violating the function's preconditions. For example, it
6820+
would make sense to put the attribute on function ``foo()`` below because
6821+
passing an incorrect size parameter would cause a buffer overflow:
68216822

6822-
.. code-block:: c++
6823+
.. code-block:: c++
68236824

6824-
[[clang::unsafe_buffer_usage]]
6825-
void foo(int *buf, size_t size) {
6826-
for (size_t i = 0; i < size; ++i) {
6827-
buf[i] = i;
6825+
[[clang::unsafe_buffer_usage]]
6826+
void foo(int *buf, size_t size) {
6827+
for (size_t i = 0; i < size; ++i) {
6828+
buf[i] = i;
6829+
}
68286830
}
6829-
}
68306831

6831-
The attribute is NOT warranted when the function uses safe abstractions,
6832-
assuming that these abstractions weren't misused outside the function.
6833-
For example, function ``bar()`` below doesn't need the attribute,
6834-
because assuming that the container ``buf`` is well-formed (has size that
6835-
fits the original buffer it refers to), overflow cannot occur:
6832+
The attribute is NOT warranted when the function uses safe abstractions,
6833+
assuming that these abstractions weren't misused outside the function.
6834+
For example, function ``bar()`` below doesn't need the attribute,
6835+
because assuming that the container ``buf`` is well-formed (has size that
6836+
fits the original buffer it refers to), overflow cannot occur:
68366837

6837-
.. code-block:: c++
6838+
.. code-block:: c++
68386839

6839-
void bar(std::span<int> buf) {
6840-
for (size_t i = 0; i < buf.size(); ++i) {
6841-
buf[i] = i;
6840+
void bar(std::span<int> buf) {
6841+
for (size_t i = 0; i < buf.size(); ++i) {
6842+
buf[i] = i;
6843+
}
68426844
}
6843-
}
68446845

6845-
In this case function ``bar()`` enables the user to keep the buffer
6846-
"containerized" in a span for as long as possible. On the other hand,
6847-
Function ``foo()`` in the previous example may have internal
6848-
consistency, but by accepting a raw buffer it requires the user to unwrap
6849-
their span, which is undesirable according to the programming model
6850-
behind ``-Wunsafe-buffer-usage``.
6846+
In this case function ``bar()`` enables the user to keep the buffer
6847+
"containerized" in a span for as long as possible. On the other hand,
6848+
Function ``foo()`` in the previous example may have internal
6849+
consistency, but by accepting a raw buffer it requires the user to unwrap
6850+
their span, which is undesirable according to the programming model
6851+
behind ``-Wunsafe-buffer-usage``.
68516852

6852-
The attribute is warranted when a function accepts a raw buffer only to
6853-
immediately put it into a span:
6853+
The attribute is warranted when a function accepts a raw buffer only to
6854+
immediately put it into a span:
68546855

6855-
.. code-block:: c++
6856+
.. code-block:: c++
68566857

6857-
[[clang::unsafe_buffer_usage]]
6858-
void baz(int *buf, size_t size) {
6859-
std::span<int> sp{ buf, size };
6860-
for (size_t i = 0; i < sp.size(); ++i) {
6861-
sp[i] = i;
6858+
[[clang::unsafe_buffer_usage]]
6859+
void baz(int *buf, size_t size) {
6860+
std::span<int> sp{ buf, size };
6861+
for (size_t i = 0; i < sp.size(); ++i) {
6862+
sp[i] = i;
6863+
}
68626864
}
6863-
}
68646865

6865-
In this case ``baz()`` does not contain any unsafe operations, but the awkward
6866-
parameter type causes the caller to unwrap the span unnecessarily.
6867-
Note that regardless of the attribute, code inside ``baz()`` isn't flagged
6868-
by ``-Wunsafe-buffer-usage`` as unsafe. It is definitely undesirable,
6869-
but if ``baz()`` is on an API surface, there is no way to improve it
6870-
to make it as safe as ``bar()`` without breaking the source and binary
6871-
compatibility with existing users of the function. In such cases
6872-
the proper solution would be to create a different function (possibly
6873-
an overload of ``baz()``) that accepts a safe container like ``bar()``,
6874-
and then use the attribute on the original ``baz()`` to help the users
6875-
update their code to use the new function.
6866+
In this case ``baz()`` does not contain any unsafe operations, but the awkward
6867+
parameter type causes the caller to unwrap the span unnecessarily.
6868+
Note that regardless of the attribute, code inside ``baz()`` isn't flagged
6869+
by ``-Wunsafe-buffer-usage`` as unsafe. It is definitely undesirable,
6870+
but if ``baz()`` is on an API surface, there is no way to improve it
6871+
to make it as safe as ``bar()`` without breaking the source and binary
6872+
compatibility with existing users of the function. In such cases
6873+
the proper solution would be to create a different function (possibly
6874+
an overload of ``baz()``) that accepts a safe container like ``bar()``,
6875+
and then use the attribute on the original ``baz()`` to help the users
6876+
update their code to use the new function.
6877+
6878+
* Attribute attached to fields: The attribute should only be attached to
6879+
struct fields, if the fields can not be updated to a safe type with bounds
6880+
check, such as std::span. In other words, the buffers prone to unsafe accesses
6881+
should always be updated to use safe containers/views and attaching the attribute
6882+
must be last resort when such an update is infeasible.
6883+
6884+
The attribute can be placed on individual fields or a set of them as shown below.
6885+
6886+
.. code-block:: c++
6887+
6888+
struct A {
6889+
[[clang::unsafe_buffer_usage]]
6890+
int *ptr1;
6891+
6892+
[[clang::unsafe_buffer_usage]]
6893+
int *ptr2, buf[10];
6894+
6895+
[[clang::unsafe_buffer_usage]]
6896+
size_t sz;
6897+
};
6898+
6899+
Here, every read/write to the fields ptr1, ptr2, buf and sz will trigger a warning
6900+
that the field has been explcitly marked as unsafe due to unsafe-buffer operations.
6901+
68766902
}];
68776903
}
68786904

clang/include/clang/Basic/DiagnosticSemaKinds.td

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12383,7 +12383,8 @@ def warn_unsafe_buffer_variable : Warning<
1238312383
InGroup<UnsafeBufferUsage>, DefaultIgnore;
1238412384
def warn_unsafe_buffer_operation : Warning<
1238512385
"%select{unsafe pointer operation|unsafe pointer arithmetic|"
12386-
"unsafe buffer access|function introduces unsafe buffer manipulation|unsafe invocation of span::data}0">,
12386+
"unsafe buffer access|function introduces unsafe buffer manipulation|unsafe invocation of span::data|"
12387+
"field %1 prone to unsafe buffer manipulation}0">,
1238712388
InGroup<UnsafeBufferUsage>, DefaultIgnore;
1238812389
def note_unsafe_buffer_operation : Note<
1238912390
"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
@@ -202,7 +202,7 @@
202202
// CHECK-NEXT: TestTypestate (SubjectMatchRule_function_is_member)
203203
// CHECK-NEXT: TrivialABI (SubjectMatchRule_record)
204204
// CHECK-NEXT: Uninitialized (SubjectMatchRule_variable_is_local)
205-
// CHECK-NEXT: UnsafeBufferUsage (SubjectMatchRule_function)
205+
// CHECK-NEXT: UnsafeBufferUsage (SubjectMatchRule_function, SubjectMatchRule_field)
206206
// CHECK-NEXT: UseHandle (SubjectMatchRule_variable_is_parameter)
207207
// CHECK-NEXT: VTablePointerAuthentication (SubjectMatchRule_record)
208208
// CHECK-NEXT: VecReturn (SubjectMatchRule_record)

0 commit comments

Comments
 (0)