[clang] [attributes][-Wunsafe-buffer-usage] Support adding unsafe_buffer_usage attribute to struct fields (PR #101585)

Malavika Samak via cfe-commits cfe-commits at lists.llvm.org
Thu Aug 8 16:02:31 PDT 2024


https://github.com/malavikasamak updated https://github.com/llvm/llvm-project/pull/101585

>From 1ecd91c03f6de92153809402b10f99e5f649787f Mon Sep 17 00:00:00 2001
From: MalavikaSamak <malavika2 at apple.com>
Date: Thu, 1 Aug 2024 11:01:36 -0700
Subject: [PATCH 1/4] [-Wunsafe-buffer-usage] Support adding
 unsafe_buffer_usage aatribute to struct fields.

---
 clang/include/clang/Basic/Attr.td             |   2 +-
 clang/include/clang/Basic/AttrDocs.td         |  45 +++++--
 .../clang/Basic/DiagnosticSemaKinds.td        |   3 +-
 clang/lib/Analysis/UnsafeBufferUsage.cpp      |  17 ++-
 clang/lib/Sema/AnalysisBasedWarnings.cpp      |  13 +-
 .../warn-unsafe-buffer-usage-field-attr.cpp   | 113 ++++++++++++++++++
 6 files changed, 176 insertions(+), 17 deletions(-)
 create mode 100644 clang/test/SemaCXX/warn-unsafe-buffer-usage-field-attr.cpp

diff --git a/clang/include/clang/Basic/Attr.td b/clang/include/clang/Basic/Attr.td
index 4825979a974d22..2cc21d67ddffb2 100644
--- a/clang/include/clang/Basic/Attr.td
+++ b/clang/include/clang/Basic/Attr.td
@@ -4447,7 +4447,7 @@ def ReleaseHandle : InheritableParamAttr {
 
 def UnsafeBufferUsage : InheritableAttr {
   let Spellings = [Clang<"unsafe_buffer_usage">];
-  let Subjects = SubjectList<[Function]>;
+  let Subjects = SubjectList<[Function, Field]>;
   let Documentation = [UnsafeBufferUsageDocs];
 }
 
diff --git a/clang/include/clang/Basic/AttrDocs.td b/clang/include/clang/Basic/AttrDocs.td
index 99738812c81579..a52e3dd68a0ce2 100644
--- a/clang/include/clang/Basic/AttrDocs.td
+++ b/clang/include/clang/Basic/AttrDocs.td
@@ -6763,15 +6763,18 @@ attribute requires a string literal argument to identify the handle being releas
 def UnsafeBufferUsageDocs : Documentation {
   let Category = DocCatFunction;
   let Content = [{
-The attribute ``[[clang::unsafe_buffer_usage]]`` should be placed on functions
-that need to be avoided as they are prone to buffer overflows. It is designed to
-work together with the off-by-default compiler warning ``-Wunsafe-buffer-usage``
-to help codebases transition away from raw pointer based buffer management,
-in favor of safer abstractions such as C++20 ``std::span``. The attribute causes
-``-Wunsafe-buffer-usage`` to warn on every use of the function, and it may
-enable ``-Wunsafe-buffer-usage`` to emit automatic fix-it hints
-which would help the user replace such unsafe functions with safe
-alternatives, though the attribute can be used even when the fix can't be automated.
+The attribute ``[[clang::unsafe_buffer_usage]]`` should be placed on functions or
+struct fields that are buffers, that must to be avoided as they are prone to 
+buffer overflows. It is designed to work together with the off-by-default compiler 
+warning ``-Wunsafe-buffer-usage``to help codebases transition away from raw pointer 
+based buffer management, in favor of safer abstractions such as C++20 ``std::span``. 
+The attribute causes ``-Wunsafe-buffer-usage`` to warn on every use of the function or 
+the field it is attached to, and it may enable ``-Wunsafe-buffer-usage`` to emit 
+automatic fix-it hints which would help the user replace the use of unsafe 
+functions(/fields) with safe alternatives, though the attribute can be used even when 
+the fix can't be automated.
+
+Attribute attached to functions:
 
 The attribute does not suppress ``-Wunsafe-buffer-usage`` inside the function
 to which it is attached. These warnings still need to be addressed.
@@ -6835,6 +6838,30 @@ the proper solution would be to create a different function (possibly
 an overload of ``baz()``) that accepts a safe container like ``bar()``,
 and then use the attribute on the original ``baz()`` to help the users
 update their code to use the new function.
+
+Attribute attached to fields:
+
+The attribute should only be attached to struct fields, if the fields can not be
+updated to a safe type with bounds check, such as std::span. In other words, the
+buffers prone to unsafe accesses should always be updated to use safe containers/views
+and attaching the attribute must be last resort when such an update is infeasible. 
+
+The attribute can be placed on individual fields or a set of them as shown below.	
+.. code-block:: c++
+
+  struct A {
+    [[clang::unsafe_buffer_usage]]
+    int *ptr1;
+  
+    [[clang::unsafe_buffer_usage]]
+    int *ptr2, buf[10];
+
+    size_t sz;
+  };
+
+Here, every read/write to the fields ptr1, ptr2 and buf will trigger a warning that the
+field is marked unsafe due to unsafe-buffer operations on it.
+
   }];
 }
 
diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td
index 810abe4f23e31e..b0428d28667a51 100644
--- a/clang/include/clang/Basic/DiagnosticSemaKinds.td
+++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td
@@ -12371,7 +12371,8 @@ def warn_unsafe_buffer_variable : Warning<
   InGroup<UnsafeBufferUsage>, DefaultIgnore;
 def warn_unsafe_buffer_operation : Warning<
   "%select{unsafe pointer operation|unsafe pointer arithmetic|"
-  "unsafe buffer access|function introduces unsafe buffer manipulation|unsafe invocation of span::data}0">,
+  "unsafe buffer access|function introduces unsafe buffer manipulation|unsafe invocation of span::data|"
+  "field %1 prone to unsafe buffer manipulation}0">,
   InGroup<UnsafeBufferUsage>, DefaultIgnore;
 def note_unsafe_buffer_operation : Note<
   "used%select{| in pointer arithmetic| in buffer access}0 here">;
diff --git a/clang/lib/Analysis/UnsafeBufferUsage.cpp b/clang/lib/Analysis/UnsafeBufferUsage.cpp
index 866222380974b6..b2645c814c3f46 100644
--- a/clang/lib/Analysis/UnsafeBufferUsage.cpp
+++ b/clang/lib/Analysis/UnsafeBufferUsage.cpp
@@ -927,21 +927,28 @@ class CArrayToPtrAssignmentGadget : public FixableGadget {
 /// over one of its pointer parameters.
 class UnsafeBufferUsageAttrGadget : public WarningGadget {
   constexpr static const char *const OpTag = "call_expr";
-  const CallExpr *Op;
+  const Expr *Op;
 
 public:
   UnsafeBufferUsageAttrGadget(const MatchFinder::MatchResult &Result)
       : WarningGadget(Kind::UnsafeBufferUsageAttr),
-        Op(Result.Nodes.getNodeAs<CallExpr>(OpTag)) {}
+        Op(Result.Nodes.getNodeAs<Expr>(OpTag)) {}
 
   static bool classof(const Gadget *G) {
     return G->getKind() == Kind::UnsafeBufferUsageAttr;
   }
 
   static Matcher matcher() {
+    auto HasUnsafeFielDecl = 
+        member(fieldDecl(allOf(
+               anyOf(hasPointerType(), hasArrayType()),
+               hasAttr(attr::UnsafeBufferUsage))));
+ 
     auto HasUnsafeFnDecl =
         callee(functionDecl(hasAttr(attr::UnsafeBufferUsage)));
-    return stmt(callExpr(HasUnsafeFnDecl).bind(OpTag));
+
+    return stmt(expr(anyOf(callExpr(HasUnsafeFnDecl).bind(OpTag), 
+            memberExpr(HasUnsafeFielDecl).bind(OpTag))));
   }
 
   void handleUnsafeOperation(UnsafeBufferUsageHandler &Handler,
@@ -959,12 +966,12 @@ class UnsafeBufferUsageAttrGadget : public WarningGadget {
 /// perform buffer operations that depend on the correctness of the parameters.
 class UnsafeBufferUsageCtorAttrGadget : public WarningGadget {
   constexpr static const char *const OpTag = "cxx_construct_expr";
-  const CXXConstructExpr *Op;
+  const Expr *Op;
 
 public:
   UnsafeBufferUsageCtorAttrGadget(const MatchFinder::MatchResult &Result)
       : WarningGadget(Kind::UnsafeBufferUsageCtorAttr),
-        Op(Result.Nodes.getNodeAs<CXXConstructExpr>(OpTag)) {}
+        Op(Result.Nodes.getNodeAs<Expr>(OpTag)) {}
 
   static bool classof(const Gadget *G) {
     return G->getKind() == Kind::UnsafeBufferUsageCtorAttr;
diff --git a/clang/lib/Sema/AnalysisBasedWarnings.cpp b/clang/lib/Sema/AnalysisBasedWarnings.cpp
index 0f604c61fa3af9..687b8df4fa2626 100644
--- a/clang/lib/Sema/AnalysisBasedWarnings.cpp
+++ b/clang/lib/Sema/AnalysisBasedWarnings.cpp
@@ -2231,6 +2231,7 @@ class UnsafeBufferUsageReporter : public UnsafeBufferUsageHandler {
     SourceLocation Loc;
     SourceRange Range;
     unsigned MsgParam = 0;
+    StringRef name = "";
     if (const auto *ASE = dyn_cast<ArraySubscriptExpr>(Operation)) {
       Loc = ASE->getBase()->getExprLoc();
       Range = ASE->getBase()->getSourceRange();
@@ -2261,6 +2262,12 @@ class UnsafeBufferUsageReporter : public UnsafeBufferUsageHandler {
         // note_unsafe_buffer_operation doesn't have this mode yet.
         assert(!IsRelatedToDecl && "Not implemented yet!");
         MsgParam = 3;
+      } else if (isa<MemberExpr>(Operation)) {
+        // note_unsafe_buffer_operation doesn't have this mode yet.
+        assert(!IsRelatedToDecl && "Not implemented yet!");
+        auto ME = dyn_cast<MemberExpr>(Operation);
+        name = ME->getMemberDecl()->getName();
+        MsgParam = 5;
       } else if (const auto *ECE = dyn_cast<ExplicitCastExpr>(Operation)) {
         QualType destType = ECE->getType();
         if (!isa<PointerType>(destType))
@@ -2285,7 +2292,11 @@ class UnsafeBufferUsageReporter : public UnsafeBufferUsageHandler {
              "Variables blamed for unsafe buffer usage without suggestions!");
       S.Diag(Loc, diag::note_unsafe_buffer_operation) << MsgParam << Range;
     } else {
-      S.Diag(Loc, diag::warn_unsafe_buffer_operation) << MsgParam << Range;
+      if(!name.empty()) {
+        S.Diag(Loc, diag::warn_unsafe_buffer_operation) << MsgParam <<name << Range;
+      } else {
+        S.Diag(Loc, diag::warn_unsafe_buffer_operation) << MsgParam << Range;
+      } 
       if (SuggestSuggestions) {
         S.Diag(Loc, diag::note_safe_buffer_usage_suggestions_disabled);
       }
diff --git a/clang/test/SemaCXX/warn-unsafe-buffer-usage-field-attr.cpp b/clang/test/SemaCXX/warn-unsafe-buffer-usage-field-attr.cpp
new file mode 100644
index 00000000000000..7650af804df9b4
--- /dev/null
+++ b/clang/test/SemaCXX/warn-unsafe-buffer-usage-field-attr.cpp
@@ -0,0 +1,113 @@
+// RUN: %clang_cc1 -std=c++20 -Wunsafe-buffer-usage \
+// RUN:            -fsafe-buffer-usage-suggestions -verify %s
+
+using size_t = __typeof(sizeof(int));
+
+namespace std {
+  class type_info;
+  class bad_cast;
+  class bad_typeid;
+
+  template <typename T> class span {
+
+  private:
+    T *elements;
+    size_t size_;
+
+  public:
+    span(T *, size_t){}
+
+    constexpr T* data() const noexcept {
+      return elements;
+    }
+
+    constexpr size_t size() const noexcept {
+      return size_;
+    }
+
+  };
+}
+
+struct A {
+    [[clang::unsafe_buffer_usage]]
+    int *ptr;
+
+    size_t sz;
+};
+
+struct B {
+   A a;
+ 
+   [[clang::unsafe_buffer_usage]]
+   int buf[];
+};
+
+union Union {
+  [[clang::unsafe_buffer_usage]]
+  int *ptr1;
+
+  int ptr2;
+};
+
+struct C {
+  Union ptr; 
+};
+
+struct D { 
+  [[clang::unsafe_buffer_usage]]
+  int *ptr, *ptr2;
+
+  [[clang::unsafe_buffer_usage]]
+  int buf[10];
+ 
+  size_t sz;
+  
+};
+
+void foo(int *ptr);
+
+void foo_safe(std::span<int> sp);
+
+void test_attribute_union(C c) {
+  int *p = c.ptr.ptr1; //expected-warning{{field ptr1 prone to unsafe buffer manipulation}}
+
+  // TODO: Warn here about the field
+  int address = c.ptr.ptr2;
+}
+
+int* test_atribute_struct(A a) {
+   int b = *(a.ptr); //expected-warning{{field ptr prone to unsafe buffer manipulation}}
+   a.sz++;
+   // expected-warning at +1{{unsafe pointer arithmetic}}
+   return a.ptr++; //expected-warning{{field ptr prone to unsafe buffer manipulation}}
+}
+
+void test_attribute_field_deref_chain(B b) {
+  int *ptr = b.a.ptr;//expected-warning{{field ptr prone to unsafe buffer manipulation}} 
+  foo(b.buf); //expected-warning{{field buf prone to unsafe buffer manipulation}}
+}
+
+void test_safe_writes(std::span<int> sp) {
+  A a;
+  // TODO: We should not warn for safe assignments from hardened types
+  a.ptr = sp.data(); //expected-warning{{field ptr prone to unsafe buffer manipulation}}
+  a.sz = sp.size();
+}
+
+void test_safe_reads(A a) {
+  //expected-warning at +1{{the two-parameter std::span construction is unsafe as it can introduce mismatch between buffer size and the bound information}}
+  std::span<int> sp {a.ptr, a.sz}; //expected-warning{{field ptr prone to unsafe buffer manipulation}}
+  foo_safe(sp);
+}
+
+void test_attribute_multiple_fields (D d) {
+   int *p =d.ptr; //expected-warning{{field ptr prone to unsafe buffer manipulation}}
+   p = d.ptr2; //expected-warning{{field ptr2 prone to unsafe buffer manipulation}}
+
+   p = d.buf; //expected-warning{{field buf prone to unsafe buffer manipulation}}
+
+   int v = d.buf[0]; //expected-warning{{field buf prone to unsafe buffer manipulation}}
+
+   //expected-warning at +1{{unsafe buffer access}}
+   v = d.buf[5]; //expected-warning{{field buf prone to unsafe buffer manipulation}}
+}

>From ffef08e561eaf9b0cf8a138d683e5683b4c0f212 Mon Sep 17 00:00:00 2001
From: MalavikaSamak <malavika2 at apple.com>
Date: Mon, 5 Aug 2024 13:11:27 -0700
Subject: [PATCH 2/4] [-Wunsafe-buffer-usage] Extend unsafe_buffer_usage attr
 support for struct fields of all types

---
 clang/include/clang/Basic/AttrDocs.td         |  30 ++--
 clang/lib/Analysis/UnsafeBufferUsage.cpp      |  18 ++-
 clang/lib/Sema/AnalysisBasedWarnings.cpp      |  11 +-
 .../warn-unsafe-buffer-usage-field-attr.cpp   | 129 +++++++++++++-----
 4 files changed, 127 insertions(+), 61 deletions(-)

diff --git a/clang/include/clang/Basic/AttrDocs.td b/clang/include/clang/Basic/AttrDocs.td
index a52e3dd68a0ce2..5c6cdd0d4be3d6 100644
--- a/clang/include/clang/Basic/AttrDocs.td
+++ b/clang/include/clang/Basic/AttrDocs.td
@@ -6763,16 +6763,15 @@ attribute requires a string literal argument to identify the handle being releas
 def UnsafeBufferUsageDocs : Documentation {
   let Category = DocCatFunction;
   let Content = [{
-The attribute ``[[clang::unsafe_buffer_usage]]`` should be placed on functions or
-struct fields that are buffers, that must to be avoided as they are prone to 
-buffer overflows. It is designed to work together with the off-by-default compiler 
-warning ``-Wunsafe-buffer-usage``to help codebases transition away from raw pointer 
-based buffer management, in favor of safer abstractions such as C++20 ``std::span``. 
-The attribute causes ``-Wunsafe-buffer-usage`` to warn on every use of the function or 
-the field it is attached to, and it may enable ``-Wunsafe-buffer-usage`` to emit 
-automatic fix-it hints which would help the user replace the use of unsafe 
-functions(/fields) with safe alternatives, though the attribute can be used even when 
-the fix can't be automated.
+The attribute ``[[clang::unsafe_buffer_usage]]`` should be placed on functions
+that need to be avoided as they are prone to buffer overflows or unsafe buffer
+struct fields. It is designed to work together with the off-by-default compiler
+warning ``-Wunsafe-buffer-usage``to help codebases transition away from raw pointer
+based buffer management, in favor of safer abstractions such as C++20 ``std::span``.
+The attribute causes ``-Wunsafe-buffer-usage`` to warn on every use of the function or
+the field it is attached to, and it may also lead to emission of automatic fix-it
+hints which would help the user replace the use of unsafe functions(/fields) with safe
+alternatives, though the attribute can be used even when the fix can't be automated.
 
 Attribute attached to functions:
 
@@ -6844,23 +6843,24 @@ Attribute attached to fields:
 The attribute should only be attached to struct fields, if the fields can not be
 updated to a safe type with bounds check, such as std::span. In other words, the
 buffers prone to unsafe accesses should always be updated to use safe containers/views
-and attaching the attribute must be last resort when such an update is infeasible. 
+and attaching the attribute must be last resort when such an update is infeasible.
 
-The attribute can be placed on individual fields or a set of them as shown below.	
+The attribute can be placed on individual fields or a set of them as shown below.
 .. code-block:: c++
 
   struct A {
     [[clang::unsafe_buffer_usage]]
     int *ptr1;
-  
+
     [[clang::unsafe_buffer_usage]]
     int *ptr2, buf[10];
 
+    [[clang::unsafe_buffer_usage]]
     size_t sz;
   };
 
-Here, every read/write to the fields ptr1, ptr2 and buf will trigger a warning that the
-field is marked unsafe due to unsafe-buffer operations on it.
+Here, every read/write to the fields ptr1, ptr2, buf and sz will trigger a warning that the
+field has been explcitly marked as unsafe due to unsafe-buffer operations.
 
   }];
 }
diff --git a/clang/lib/Analysis/UnsafeBufferUsage.cpp b/clang/lib/Analysis/UnsafeBufferUsage.cpp
index b2645c814c3f46..b28f46aef9dfcd 100644
--- a/clang/lib/Analysis/UnsafeBufferUsage.cpp
+++ b/clang/lib/Analysis/UnsafeBufferUsage.cpp
@@ -926,7 +926,7 @@ class CArrayToPtrAssignmentGadget : public FixableGadget {
 /// A call of a function or method that performs unchecked buffer operations
 /// over one of its pointer parameters.
 class UnsafeBufferUsageAttrGadget : public WarningGadget {
-  constexpr static const char *const OpTag = "call_expr";
+  constexpr static const char *const OpTag = "attr_expr";
   const Expr *Op;
 
 public:
@@ -939,16 +939,14 @@ class UnsafeBufferUsageAttrGadget : public WarningGadget {
   }
 
   static Matcher matcher() {
-    auto HasUnsafeFielDecl = 
-        member(fieldDecl(allOf(
-               anyOf(hasPointerType(), hasArrayType()),
-               hasAttr(attr::UnsafeBufferUsage))));
- 
+    auto HasUnsafeFielDecl =
+        member(fieldDecl(hasAttr(attr::UnsafeBufferUsage)));
+
     auto HasUnsafeFnDecl =
         callee(functionDecl(hasAttr(attr::UnsafeBufferUsage)));
 
-    return stmt(expr(anyOf(callExpr(HasUnsafeFnDecl).bind(OpTag), 
-            memberExpr(HasUnsafeFielDecl).bind(OpTag))));
+    return stmt(expr(anyOf(callExpr(HasUnsafeFnDecl).bind(OpTag),
+                           memberExpr(HasUnsafeFielDecl).bind(OpTag))));
   }
 
   void handleUnsafeOperation(UnsafeBufferUsageHandler &Handler,
@@ -966,12 +964,12 @@ class UnsafeBufferUsageAttrGadget : public WarningGadget {
 /// perform buffer operations that depend on the correctness of the parameters.
 class UnsafeBufferUsageCtorAttrGadget : public WarningGadget {
   constexpr static const char *const OpTag = "cxx_construct_expr";
-  const Expr *Op;
+  const CXXConstructExpr *Op;
 
 public:
   UnsafeBufferUsageCtorAttrGadget(const MatchFinder::MatchResult &Result)
       : WarningGadget(Kind::UnsafeBufferUsageCtorAttr),
-        Op(Result.Nodes.getNodeAs<Expr>(OpTag)) {}
+        Op(Result.Nodes.getNodeAs<CXXConstructExpr>(OpTag)) {}
 
   static bool classof(const Gadget *G) {
     return G->getKind() == Kind::UnsafeBufferUsageCtorAttr;
diff --git a/clang/lib/Sema/AnalysisBasedWarnings.cpp b/clang/lib/Sema/AnalysisBasedWarnings.cpp
index 687b8df4fa2626..e6ce89dc7ec406 100644
--- a/clang/lib/Sema/AnalysisBasedWarnings.cpp
+++ b/clang/lib/Sema/AnalysisBasedWarnings.cpp
@@ -2231,7 +2231,7 @@ class UnsafeBufferUsageReporter : public UnsafeBufferUsageHandler {
     SourceLocation Loc;
     SourceRange Range;
     unsigned MsgParam = 0;
-    StringRef name = "";
+    NamedDecl *D = nullptr;
     if (const auto *ASE = dyn_cast<ArraySubscriptExpr>(Operation)) {
       Loc = ASE->getBase()->getExprLoc();
       Range = ASE->getBase()->getSourceRange();
@@ -2266,7 +2266,7 @@ class UnsafeBufferUsageReporter : public UnsafeBufferUsageHandler {
         // note_unsafe_buffer_operation doesn't have this mode yet.
         assert(!IsRelatedToDecl && "Not implemented yet!");
         auto ME = dyn_cast<MemberExpr>(Operation);
-        name = ME->getMemberDecl()->getName();
+        D = ME->getMemberDecl();
         MsgParam = 5;
       } else if (const auto *ECE = dyn_cast<ExplicitCastExpr>(Operation)) {
         QualType destType = ECE->getType();
@@ -2292,11 +2292,12 @@ class UnsafeBufferUsageReporter : public UnsafeBufferUsageHandler {
              "Variables blamed for unsafe buffer usage without suggestions!");
       S.Diag(Loc, diag::note_unsafe_buffer_operation) << MsgParam << Range;
     } else {
-      if(!name.empty()) {
-        S.Diag(Loc, diag::warn_unsafe_buffer_operation) << MsgParam <<name << Range;
+      if (D) {
+        S.Diag(Loc, diag::warn_unsafe_buffer_operation)
+            << MsgParam << D << Range;
       } else {
         S.Diag(Loc, diag::warn_unsafe_buffer_operation) << MsgParam << Range;
-      } 
+      }
       if (SuggestSuggestions) {
         S.Diag(Loc, diag::note_safe_buffer_usage_suggestions_disabled);
       }
diff --git a/clang/test/SemaCXX/warn-unsafe-buffer-usage-field-attr.cpp b/clang/test/SemaCXX/warn-unsafe-buffer-usage-field-attr.cpp
index 7650af804df9b4..64d5e1e3fad7ff 100644
--- a/clang/test/SemaCXX/warn-unsafe-buffer-usage-field-attr.cpp
+++ b/clang/test/SemaCXX/warn-unsafe-buffer-usage-field-attr.cpp
@@ -42,17 +42,6 @@ struct B {
    int buf[];
 };
 
-union Union {
-  [[clang::unsafe_buffer_usage]]
-  int *ptr1;
-
-  int ptr2;
-};
-
-struct C {
-  Union ptr; 
-};
-
 struct D { 
   [[clang::unsafe_buffer_usage]]
   int *ptr, *ptr2;
@@ -68,46 +57,124 @@ void foo(int *ptr);
 
 void foo_safe(std::span<int> sp);
 
-void test_attribute_union(C c) {
-  int *p = c.ptr.ptr1; //expected-warning{{field ptr1 prone to unsafe buffer manipulation}}
-
-  // TODO: Warn here about the field
-  int address = c.ptr.ptr2;
-}
-
 int* test_atribute_struct(A a) {
-   int b = *(a.ptr); //expected-warning{{field ptr prone to unsafe buffer manipulation}}
+   int b = *(a.ptr); //expected-warning{{field 'ptr' prone to unsafe buffer manipulation}}
    a.sz++;
    // expected-warning at +1{{unsafe pointer arithmetic}}
-   return a.ptr++; //expected-warning{{field ptr prone to unsafe buffer manipulation}}
+   return a.ptr++; //expected-warning{{field 'ptr' prone to unsafe buffer manipulation}}
 }
 
 void test_attribute_field_deref_chain(B b) {
-  int *ptr = b.a.ptr;//expected-warning{{field ptr prone to unsafe buffer manipulation}} 
-  foo(b.buf); //expected-warning{{field buf prone to unsafe buffer manipulation}}
+  int *ptr = b.a.ptr;//expected-warning{{field 'ptr' prone to unsafe buffer manipulation}}
+  foo(b.buf); //expected-warning{{field 'buf' prone to unsafe buffer manipulation}}
 }
 
 void test_safe_writes(std::span<int> sp) {
   A a;
   // TODO: We should not warn for safe assignments from hardened types
-  a.ptr = sp.data(); //expected-warning{{field ptr prone to unsafe buffer manipulation}}
+  a.ptr = sp.data(); //expected-warning{{field 'ptr' prone to unsafe buffer manipulation}}
   a.sz = sp.size();
+
+  a.ptr = nullptr; // expected-warning{{field 'ptr' prone to unsafe buffer manipulation}}
 }
 
-void test_safe_reads(A a) {
+void test_safe_reads(A a, A b) {
   //expected-warning at +1{{the two-parameter std::span construction is unsafe as it can introduce mismatch between buffer size and the bound information}}
-  std::span<int> sp {a.ptr, a.sz}; //expected-warning{{field ptr prone to unsafe buffer manipulation}}
-  foo_safe(sp);
+  std::span<int> sp {a.ptr, a.sz}; //expected-warning{{field 'ptr' prone to unsafe buffer manipulation}}
+
+  // expected-warning at +1 3{{field 'ptr' prone to unsafe buffer manipulation}}
+  if(a.ptr != nullptr && a.ptr != b.ptr) {
+    foo_safe(sp);
+  }
+
 }
 
 void test_attribute_multiple_fields (D d) {
-   int *p =d.ptr; //expected-warning{{field ptr prone to unsafe buffer manipulation}}
-   p = d.ptr2; //expected-warning{{field ptr2 prone to unsafe buffer manipulation}}
+   int *p =d.ptr; //expected-warning{{field 'ptr' prone to unsafe buffer manipulation}}
+   p = d.ptr2; //expected-warning{{field 'ptr2' prone to unsafe buffer manipulation}}
 
-   p = d.buf; //expected-warning{{field buf prone to unsafe buffer manipulation}}
+   p = d.buf; //expected-warning{{field 'buf' prone to unsafe buffer manipulation}}
 
-   int v = d.buf[0]; //expected-warning{{field buf prone to unsafe buffer manipulation}}
+   int v = d.buf[0]; //expected-warning{{field 'buf' prone to unsafe buffer manipulation}}
 
    //expected-warning at +1{{unsafe buffer access}}
-   v = d.buf[5]; //expected-warning{{field buf prone to unsafe buffer manipulation}}
+   v = d.buf[5]; //expected-warning{{field 'buf' prone to unsafe buffer manipulation}}
+}
+
+template <typename T>
+struct TemplateArray {
+  [[clang::unsafe_buffer_usage]]
+  T *buf;
+
+  [[clang::unsafe_buffer_usage]]
+  size_t sz;
+};
+
+
+void test_struct_template (TemplateArray<int> t) {
+  int *p = t.buf; //expected-warning{{field 'buf' prone to unsafe buffer manipulation}}
+  size_t s = t.sz; //expected-warning{{field 'sz' prone to unsafe buffer manipulation}}
+}
+
+class R {
+  [[clang::unsafe_buffer_usage]]
+  int *array;
+
+  public:
+   int* getArray() {
+     return array; //expected-warning{{field 'array' prone to unsafe buffer manipulation}}
+   }
+ 
+   void setArray(int *arr) {
+     array = arr; //expected-warning{{field 'array' prone to unsafe buffer manipulation}}
+   }
+};
+
+template<class P>
+class Q {
+  [[clang::unsafe_buffer_usage]]
+  P *array;
+
+  public:
+   P* getArray() {
+     return array; //expected-warning{{field 'array' prone to unsafe buffer manipulation}}
+   }
+
+   void setArray(P *arr) {
+     array = arr; //expected-warning{{field 'array' prone to unsafe buffer manipulation}}
+   }
+};
+
+void test_class_template(Q<R> q) {
+   q.getArray();
+   q.setArray(nullptr);
+}
+
+struct AnonFields {
+ struct {
+  [[clang::unsafe_buffer_usage]]
+  int a;
+ };
+};
+
+void test_anon_fields(AnonFields anon) {
+  int val = anon.a; //expected-warning{{field 'a' prone to unsafe buffer manipulation}}
+}
+
+union Union {
+  [[clang::unsafe_buffer_usage]]
+  int *ptr1;
+
+  int ptr2;
+};
+
+struct C {
+  Union ptr;
+};
+
+void test_attribute_union(C c) {
+  int *p = c.ptr.ptr1; //expected-warning{{field 'ptr1' prone to unsafe buffer manipulation}}
+
+  // TODO: Warn here about the field
+  int address = c.ptr.ptr2;
 }

>From 9fe652bd8d5d263e6e10840de76bd8cf5f95ea27 Mon Sep 17 00:00:00 2001
From: MalavikaSamak <malavika2 at apple.com>
Date: Thu, 8 Aug 2024 14:36:24 -0700
Subject: [PATCH 3/4] Addressing recent comments

---
 clang/include/clang/Basic/AttrDocs.td         | 139 +++++++++---------
 clang/lib/Analysis/UnsafeBufferUsage.cpp      |   4 +-
 .../warn-unsafe-buffer-usage-field-attr.cpp   |   6 +-
 3 files changed, 73 insertions(+), 76 deletions(-)

diff --git a/clang/include/clang/Basic/AttrDocs.td b/clang/include/clang/Basic/AttrDocs.td
index 5c6cdd0d4be3d6..3cc6edad98599d 100644
--- a/clang/include/clang/Basic/AttrDocs.td
+++ b/clang/include/clang/Basic/AttrDocs.td
@@ -6773,94 +6773,93 @@ the field it is attached to, and it may also lead to emission of automatic fix-i
 hints which would help the user replace the use of unsafe functions(/fields) with safe
 alternatives, though the attribute can be used even when the fix can't be automated.
 
-Attribute attached to functions:
+* Attribute attached to functions: The attribute does not suppress 
+  ``-Wunsafe-buffer-usage`` inside the function to which it is attached. 
+  These warnings still need to be addressed.
 
-The attribute does not suppress ``-Wunsafe-buffer-usage`` inside the function
-to which it is attached. These warnings still need to be addressed.
+  The attribute is warranted even if the only way a function can overflow
+  the buffer is by violating the function's preconditions. For example, it
+  would make sense to put the attribute on function ``foo()`` below because
+  passing an incorrect size parameter would cause a buffer overflow:
 
-The attribute is warranted even if the only way a function can overflow
-the buffer is by violating the function's preconditions. For example, it
-would make sense to put the attribute on function ``foo()`` below because
-passing an incorrect size parameter would cause a buffer overflow:
-
-.. code-block:: c++
+  .. code-block:: c++
 
-  [[clang::unsafe_buffer_usage]]
-  void foo(int *buf, size_t size) {
-    for (size_t i = 0; i < size; ++i) {
-      buf[i] = i;
+    [[clang::unsafe_buffer_usage]]
+    void foo(int *buf, size_t size) {
+      for (size_t i = 0; i < size; ++i) {
+        buf[i] = i;
+      }
     }
-  }
 
-The attribute is NOT warranted when the function uses safe abstractions,
-assuming that these abstractions weren't misused outside the function.
-For example, function ``bar()`` below doesn't need the attribute,
-because assuming that the container ``buf`` is well-formed (has size that
-fits the original buffer it refers to), overflow cannot occur:
+  The attribute is NOT warranted when the function uses safe abstractions,
+  assuming that these abstractions weren't misused outside the function.
+  For example, function ``bar()`` below doesn't need the attribute,
+  because assuming that the container ``buf`` is well-formed (has size that
+  fits the original buffer it refers to), overflow cannot occur:
 
-.. code-block:: c++
+  .. code-block:: c++
 
-  void bar(std::span<int> buf) {
-    for (size_t i = 0; i < buf.size(); ++i) {
-      buf[i] = i;
+    void bar(std::span<int> buf) {
+      for (size_t i = 0; i < buf.size(); ++i) {
+        buf[i] = i;
+      }
     }
-  }
 
-In this case function ``bar()`` enables the user to keep the buffer
-"containerized" in a span for as long as possible. On the other hand,
-Function ``foo()`` in the previous example may have internal
-consistency, but by accepting a raw buffer it requires the user to unwrap
-their span, which is undesirable according to the programming model
-behind ``-Wunsafe-buffer-usage``.
+  In this case function ``bar()`` enables the user to keep the buffer
+  "containerized" in a span for as long as possible. On the other hand,
+  Function ``foo()`` in the previous example may have internal
+  consistency, but by accepting a raw buffer it requires the user to unwrap
+  their span, which is undesirable according to the programming model
+  behind ``-Wunsafe-buffer-usage``.
 
-The attribute is warranted when a function accepts a raw buffer only to
-immediately put it into a span:
+  The attribute is warranted when a function accepts a raw buffer only to
+  immediately put it into a span:
 
-.. code-block:: c++
+  .. code-block:: c++
 
-  [[clang::unsafe_buffer_usage]]
-  void baz(int *buf, size_t size) {
-    std::span<int> sp{ buf, size };
-    for (size_t i = 0; i < sp.size(); ++i) {
-      sp[i] = i;
+    [[clang::unsafe_buffer_usage]]
+    void baz(int *buf, size_t size) {
+      std::span<int> sp{ buf, size };
+      for (size_t i = 0; i < sp.size(); ++i) {
+        sp[i] = i;
+      }
     }
-  }
 
-In this case ``baz()`` does not contain any unsafe operations, but the awkward
-parameter type causes the caller to unwrap the span unnecessarily.
-Note that regardless of the attribute, code inside ``baz()`` isn't flagged
-by ``-Wunsafe-buffer-usage`` as unsafe. It is definitely undesirable,
-but if ``baz()`` is on an API surface, there is no way to improve it
-to make it as safe as ``bar()`` without breaking the source and binary
-compatibility with existing users of the function. In such cases
-the proper solution would be to create a different function (possibly
-an overload of ``baz()``) that accepts a safe container like ``bar()``,
-and then use the attribute on the original ``baz()`` to help the users
-update their code to use the new function.
-
-Attribute attached to fields:
-
-The attribute should only be attached to struct fields, if the fields can not be
-updated to a safe type with bounds check, such as std::span. In other words, the
-buffers prone to unsafe accesses should always be updated to use safe containers/views
-and attaching the attribute must be last resort when such an update is infeasible.
-
-The attribute can be placed on individual fields or a set of them as shown below.
-.. code-block:: c++
+  In this case ``baz()`` does not contain any unsafe operations, but the awkward
+  parameter type causes the caller to unwrap the span unnecessarily.
+  Note that regardless of the attribute, code inside ``baz()`` isn't flagged
+  by ``-Wunsafe-buffer-usage`` as unsafe. It is definitely undesirable,
+  but if ``baz()`` is on an API surface, there is no way to improve it
+  to make it as safe as ``bar()`` without breaking the source and binary
+  compatibility with existing users of the function. In such cases
+  the proper solution would be to create a different function (possibly
+  an overload of ``baz()``) that accepts a safe container like ``bar()``,
+  and then use the attribute on the original ``baz()`` to help the users
+  update their code to use the new function.
+
+* Attribute attached to fields: The attribute should only be attached to 
+  struct fields, if the fields can not be updated to a safe type with bounds 
+  check, such as std::span. In other words, the buffers prone to unsafe accesses 
+  should always be updated to use safe containers/views and attaching the attribute 
+  must be last resort when such an update is infeasible.
+
+  The attribute can be placed on individual fields or a set of them as shown below.
 
-  struct A {
-    [[clang::unsafe_buffer_usage]]
-    int *ptr1;
+  .. code-block:: c++
 
-    [[clang::unsafe_buffer_usage]]
-    int *ptr2, buf[10];
+    struct A {
+      [[clang::unsafe_buffer_usage]]
+      int *ptr1;
 
-    [[clang::unsafe_buffer_usage]]
-    size_t sz;
-  };
+      [[clang::unsafe_buffer_usage]]
+      int *ptr2, buf[10];
+
+      [[clang::unsafe_buffer_usage]]
+      size_t sz;
+    };
 
-Here, every read/write to the fields ptr1, ptr2, buf and sz will trigger a warning that the
-field has been explcitly marked as unsafe due to unsafe-buffer operations.
+  Here, every read/write to the fields ptr1, ptr2, buf and sz will trigger a warning 
+  that the field has been explcitly marked as unsafe due to unsafe-buffer operations.
 
   }];
 }
diff --git a/clang/lib/Analysis/UnsafeBufferUsage.cpp b/clang/lib/Analysis/UnsafeBufferUsage.cpp
index b28f46aef9dfcd..b1be0ea4d27b0a 100644
--- a/clang/lib/Analysis/UnsafeBufferUsage.cpp
+++ b/clang/lib/Analysis/UnsafeBufferUsage.cpp
@@ -945,8 +945,8 @@ class UnsafeBufferUsageAttrGadget : public WarningGadget {
     auto HasUnsafeFnDecl =
         callee(functionDecl(hasAttr(attr::UnsafeBufferUsage)));
 
-    return stmt(expr(anyOf(callExpr(HasUnsafeFnDecl).bind(OpTag),
-                           memberExpr(HasUnsafeFielDecl).bind(OpTag))));
+    return stmt(anyOf(callExpr(HasUnsafeFnDecl).bind(OpTag),
+                           memberExpr(HasUnsafeFielDecl).bind(OpTag)));
   }
 
   void handleUnsafeOperation(UnsafeBufferUsageHandler &Handler,
diff --git a/clang/test/SemaCXX/warn-unsafe-buffer-usage-field-attr.cpp b/clang/test/SemaCXX/warn-unsafe-buffer-usage-field-attr.cpp
index 64d5e1e3fad7ff..2551d8e9a4db42 100644
--- a/clang/test/SemaCXX/warn-unsafe-buffer-usage-field-attr.cpp
+++ b/clang/test/SemaCXX/warn-unsafe-buffer-usage-field-attr.cpp
@@ -69,16 +69,15 @@ void test_attribute_field_deref_chain(B b) {
   foo(b.buf); //expected-warning{{field 'buf' prone to unsafe buffer manipulation}}
 }
 
-void test_safe_writes(std::span<int> sp) {
+void test_writes_from_span(std::span<int> sp) {
   A a;
-  // TODO: We should not warn for safe assignments from hardened types
   a.ptr = sp.data(); //expected-warning{{field 'ptr' prone to unsafe buffer manipulation}}
   a.sz = sp.size();
 
   a.ptr = nullptr; // expected-warning{{field 'ptr' prone to unsafe buffer manipulation}}
 }
 
-void test_safe_reads(A a, A b) {
+void test_reads_to_span(A a, A b) {
   //expected-warning at +1{{the two-parameter std::span construction is unsafe as it can introduce mismatch between buffer size and the bound information}}
   std::span<int> sp {a.ptr, a.sz}; //expected-warning{{field 'ptr' prone to unsafe buffer manipulation}}
 
@@ -175,6 +174,5 @@ struct C {
 void test_attribute_union(C c) {
   int *p = c.ptr.ptr1; //expected-warning{{field 'ptr1' prone to unsafe buffer manipulation}}
 
-  // TODO: Warn here about the field
   int address = c.ptr.ptr2;
 }

>From a214f888e19c70d507b25c14ac43a63be3516ddb Mon Sep 17 00:00:00 2001
From: MalavikaSamak <malavika2 at apple.com>
Date: Thu, 8 Aug 2024 15:50:39 -0700
Subject: [PATCH 4/4] Fixing clang-format issue

---
 clang/lib/Analysis/UnsafeBufferUsage.cpp | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/clang/lib/Analysis/UnsafeBufferUsage.cpp b/clang/lib/Analysis/UnsafeBufferUsage.cpp
index b1be0ea4d27b0a..e343c2c48105c1 100644
--- a/clang/lib/Analysis/UnsafeBufferUsage.cpp
+++ b/clang/lib/Analysis/UnsafeBufferUsage.cpp
@@ -946,7 +946,7 @@ class UnsafeBufferUsageAttrGadget : public WarningGadget {
         callee(functionDecl(hasAttr(attr::UnsafeBufferUsage)));
 
     return stmt(anyOf(callExpr(HasUnsafeFnDecl).bind(OpTag),
-                           memberExpr(HasUnsafeFielDecl).bind(OpTag)));
+                      memberExpr(HasUnsafeFielDecl).bind(OpTag)));
   }
 
   void handleUnsafeOperation(UnsafeBufferUsageHandler &Handler,



More information about the cfe-commits mailing list