Skip to content

Commit c9f2b13

Browse files
author
MalavikaSamak
committed
[attributes][-Wunsafe-buffer-usage] Support adding unsafe_buffer_usage attribute to struct fields.
Extend unsafe_buffer_usage attribute to support struct fields. Adding this attribute to the fields causes the compiler to issue a warning at every access site of the field. The attribute should be attached to struct fields to inform the clients of the struct that the warned fields are prone OOB accesses.
1 parent 5b4e5f8 commit c9f2b13

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

44484448
def UnsafeBufferUsage : InheritableAttr {
44494449
let Spellings = [Clang<"unsafe_buffer_usage">];
4450-
let Subjects = SubjectList<[Function]>;
4450+
let Subjects = SubjectList<[Function, Field]>;
44514451
let Documentation = [UnsafeBufferUsageDocs];
44524452
}
44534453

clang/include/clang/Basic/AttrDocs.td

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

6776-
The attribute does not suppress ``-Wunsafe-buffer-usage`` inside the function
6777-
to which it is attached. These warnings still need to be addressed.
6776+
* Attribute attached to functions: The attribute does not suppress
6777+
``-Wunsafe-buffer-usage`` inside the function to which it is attached.
6778+
These warnings still need to be addressed.
67786779

6779-
The attribute is warranted even if the only way a function can overflow
6780-
the buffer is by violating the function's preconditions. For example, it
6781-
would make sense to put the attribute on function ``foo()`` below because
6782-
passing an incorrect size parameter would cause a buffer overflow:
6780+
The attribute is warranted even if the only way a function can overflow
6781+
the buffer is by violating the function's preconditions. For example, it
6782+
would make sense to put the attribute on function ``foo()`` below because
6783+
passing an incorrect size parameter would cause a buffer overflow:
67836784

6784-
.. code-block:: c++
6785+
.. code-block:: c++
67856786

6786-
[[clang::unsafe_buffer_usage]]
6787-
void foo(int *buf, size_t size) {
6788-
for (size_t i = 0; i < size; ++i) {
6789-
buf[i] = i;
6787+
[[clang::unsafe_buffer_usage]]
6788+
void foo(int *buf, size_t size) {
6789+
for (size_t i = 0; i < size; ++i) {
6790+
buf[i] = i;
6791+
}
67906792
}
6791-
}
67926793

6793-
The attribute is NOT warranted when the function uses safe abstractions,
6794-
assuming that these abstractions weren't misused outside the function.
6795-
For example, function ``bar()`` below doesn't need the attribute,
6796-
because assuming that the container ``buf`` is well-formed (has size that
6797-
fits the original buffer it refers to), overflow cannot occur:
6794+
The attribute is NOT warranted when the function uses safe abstractions,
6795+
assuming that these abstractions weren't misused outside the function.
6796+
For example, function ``bar()`` below doesn't need the attribute,
6797+
because assuming that the container ``buf`` is well-formed (has size that
6798+
fits the original buffer it refers to), overflow cannot occur:
67986799

6799-
.. code-block:: c++
6800+
.. code-block:: c++
68006801

6801-
void bar(std::span<int> buf) {
6802-
for (size_t i = 0; i < buf.size(); ++i) {
6803-
buf[i] = i;
6802+
void bar(std::span<int> buf) {
6803+
for (size_t i = 0; i < buf.size(); ++i) {
6804+
buf[i] = i;
6805+
}
68046806
}
6805-
}
68066807

6807-
In this case function ``bar()`` enables the user to keep the buffer
6808-
"containerized" in a span for as long as possible. On the other hand,
6809-
Function ``foo()`` in the previous example may have internal
6810-
consistency, but by accepting a raw buffer it requires the user to unwrap
6811-
their span, which is undesirable according to the programming model
6812-
behind ``-Wunsafe-buffer-usage``.
6808+
In this case function ``bar()`` enables the user to keep the buffer
6809+
"containerized" in a span for as long as possible. On the other hand,
6810+
Function ``foo()`` in the previous example may have internal
6811+
consistency, but by accepting a raw buffer it requires the user to unwrap
6812+
their span, which is undesirable according to the programming model
6813+
behind ``-Wunsafe-buffer-usage``.
68136814

6814-
The attribute is warranted when a function accepts a raw buffer only to
6815-
immediately put it into a span:
6815+
The attribute is warranted when a function accepts a raw buffer only to
6816+
immediately put it into a span:
68166817

6817-
.. code-block:: c++
6818+
.. code-block:: c++
68186819

6819-
[[clang::unsafe_buffer_usage]]
6820-
void baz(int *buf, size_t size) {
6821-
std::span<int> sp{ buf, size };
6822-
for (size_t i = 0; i < sp.size(); ++i) {
6823-
sp[i] = i;
6820+
[[clang::unsafe_buffer_usage]]
6821+
void baz(int *buf, size_t size) {
6822+
std::span<int> sp{ buf, size };
6823+
for (size_t i = 0; i < sp.size(); ++i) {
6824+
sp[i] = i;
6825+
}
68246826
}
6825-
}
68266827

6827-
In this case ``baz()`` does not contain any unsafe operations, but the awkward
6828-
parameter type causes the caller to unwrap the span unnecessarily.
6829-
Note that regardless of the attribute, code inside ``baz()`` isn't flagged
6830-
by ``-Wunsafe-buffer-usage`` as unsafe. It is definitely undesirable,
6831-
but if ``baz()`` is on an API surface, there is no way to improve it
6832-
to make it as safe as ``bar()`` without breaking the source and binary
6833-
compatibility with existing users of the function. In such cases
6834-
the proper solution would be to create a different function (possibly
6835-
an overload of ``baz()``) that accepts a safe container like ``bar()``,
6836-
and then use the attribute on the original ``baz()`` to help the users
6837-
update their code to use the new function.
6828+
In this case ``baz()`` does not contain any unsafe operations, but the awkward
6829+
parameter type causes the caller to unwrap the span unnecessarily.
6830+
Note that regardless of the attribute, code inside ``baz()`` isn't flagged
6831+
by ``-Wunsafe-buffer-usage`` as unsafe. It is definitely undesirable,
6832+
but if ``baz()`` is on an API surface, there is no way to improve it
6833+
to make it as safe as ``bar()`` without breaking the source and binary
6834+
compatibility with existing users of the function. In such cases
6835+
the proper solution would be to create a different function (possibly
6836+
an overload of ``baz()``) that accepts a safe container like ``bar()``,
6837+
and then use the attribute on the original ``baz()`` to help the users
6838+
update their code to use the new function.
6839+
6840+
* Attribute attached to fields: The attribute should only be attached to
6841+
struct fields, if the fields can not be updated to a safe type with bounds
6842+
check, such as std::span. In other words, the buffers prone to unsafe accesses
6843+
should always be updated to use safe containers/views and attaching the attribute
6844+
must be last resort when such an update is infeasible.
6845+
6846+
The attribute can be placed on individual fields or a set of them as shown below.
6847+
6848+
.. code-block:: c++
6849+
6850+
struct A {
6851+
[[clang::unsafe_buffer_usage]]
6852+
int *ptr1;
6853+
6854+
[[clang::unsafe_buffer_usage]]
6855+
int *ptr2, buf[10];
6856+
6857+
[[clang::unsafe_buffer_usage]]
6858+
size_t sz;
6859+
};
6860+
6861+
Here, every read/write to the fields ptr1, ptr2, buf and sz will trigger a warning
6862+
that the field has been explcitly marked as unsafe due to unsafe-buffer operations.
6863+
68386864
}];
68396865
}
68406866

clang/include/clang/Basic/DiagnosticSemaKinds.td

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12371,7 +12371,8 @@ def warn_unsafe_buffer_variable : Warning<
1237112371
InGroup<UnsafeBufferUsage>, DefaultIgnore;
1237212372
def warn_unsafe_buffer_operation : Warning<
1237312373
"%select{unsafe pointer operation|unsafe pointer arithmetic|"
12374-
"unsafe buffer access|function introduces unsafe buffer manipulation|unsafe invocation of span::data}0">,
12374+
"unsafe buffer access|function introduces unsafe buffer manipulation|unsafe invocation of span::data|"
12375+
"field %1 prone to unsafe buffer manipulation}0">,
1237512376
InGroup<UnsafeBufferUsage>, DefaultIgnore;
1237612377
def note_unsafe_buffer_operation : Note<
1237712378
"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
@@ -200,7 +200,7 @@
200200
// CHECK-NEXT: TestTypestate (SubjectMatchRule_function_is_member)
201201
// CHECK-NEXT: TrivialABI (SubjectMatchRule_record)
202202
// CHECK-NEXT: Uninitialized (SubjectMatchRule_variable_is_local)
203-
// CHECK-NEXT: UnsafeBufferUsage (SubjectMatchRule_function)
203+
// CHECK-NEXT: UnsafeBufferUsage (SubjectMatchRule_function, SubjectMatchRule_field)
204204
// CHECK-NEXT: UseHandle (SubjectMatchRule_variable_is_parameter)
205205
// CHECK-NEXT: VTablePointerAuthentication (SubjectMatchRule_record)
206206
// CHECK-NEXT: VecReturn (SubjectMatchRule_record)

0 commit comments

Comments
 (0)