llvmbot wrote:

<!--LLVM PR SUMMARY COMMENT-->

@llvm/pr-subscribers-clang-analysis

Author: Ziqing Luo (ziqingluo-90)

<details>
<summary>Changes</summary>

Support safe construction of `std::span` from `begin` and `end` calls on 
hardened containers or views or `std::initializer_list`s.

For example, the following code is safe:
```
void create(std::initializer_list&lt;int&gt; il) {
    std::span&lt;int&gt; input{ il.begin(), il.end() }; // no warn
}
```

rdar://152637380

---
Full diff: https://github.com/llvm/llvm-project/pull/145311.diff


2 Files Affected:

- (modified) clang/lib/Analysis/UnsafeBufferUsage.cpp (+34-5) 
- (modified) 
clang/test/SemaCXX/warn-unsafe-buffer-usage-in-container-span-construct.cpp 
(+60) 


``````````diff
diff --git a/clang/lib/Analysis/UnsafeBufferUsage.cpp 
b/clang/lib/Analysis/UnsafeBufferUsage.cpp
index 631a546b45ff4..259db3f092c92 100644
--- a/clang/lib/Analysis/UnsafeBufferUsage.cpp
+++ b/clang/lib/Analysis/UnsafeBufferUsage.cpp
@@ -115,6 +115,10 @@ class MatchResult {
 };
 } // namespace
 
+#define SIZED_CONTAINER_OR_VIEW_LIST                                           
\
+  "span", "array", "vector", "basic_string_view", "basic_string",              
\
+      "initializer_list",
+
 // A `RecursiveASTVisitor` that traverses all descendants of a given node "n"
 // except for those belonging to a different callable of "n".
 class MatchDescendantVisitor : public DynamicRecursiveASTVisitor {
@@ -463,6 +467,8 @@ static bool areEqualIntegers(const Expr *E1, const Expr 
*E2, ASTContext &Ctx) {
 //       `N, M` are parameter indexes to the allocating element number and 
size.
 //        Sometimes, there is only one parameter index representing the total
 //        size.
+//   7. `std::span<T>{x.begin(), x.end()}` where `x` is an object in the
+//      SIZED_CONTAINER_OR_VIEW_LIST.
 static bool isSafeSpanTwoParamConstruct(const CXXConstructExpr &Node,
                                         ASTContext &Ctx) {
   assert(Node.getNumArgs() == 2 &&
@@ -560,6 +566,31 @@ static bool isSafeSpanTwoParamConstruct(const 
CXXConstructExpr &Node,
         }
     }
   }
+  // Check form 7:
+  auto IsMethodCallToSizedObject = [](const Stmt *Node, StringRef MethodName) {
+    if (const auto *MC = dyn_cast<CXXMemberCallExpr>(Node)) {
+      const auto *MD = MC->getMethodDecl();
+      const auto *RD = MC->getRecordDecl();
+
+      if (auto *II = RD->getDeclName().getAsIdentifierInfo();
+          II && RD->isInStdNamespace())
+        return llvm::is_contained({SIZED_CONTAINER_OR_VIEW_LIST},
+                                  II->getName()) &&
+               MD->getName() == MethodName;
+    }
+    return false;
+  };
+
+  if (IsMethodCallToSizedObject(Arg0, "begin") &&
+      IsMethodCallToSizedObject(Arg1, "end"))
+    return AreSameDRE(
+        // We know Arg0 and Arg1 are `CXXMemberCallExpr`s:
+        cast<CXXMemberCallExpr>(Arg0)
+            ->getImplicitObjectArgument()
+            ->IgnoreParenImpCasts(),
+        cast<CXXMemberCallExpr>(Arg1)
+            ->getImplicitObjectArgument()
+            ->IgnoreParenImpCasts());
   return false;
 }
 
@@ -1058,8 +1089,7 @@ static bool hasUnsafeSnprintfBuffer(const CallExpr &Node,
     return false; // not an snprintf call
 
   // Pattern 1:
-  static StringRef SizedObjs[] = {"span", "array", "vector",
-                                  "basic_string_view", "basic_string"};
+  static StringRef SizedObjs[] = {SIZED_CONTAINER_OR_VIEW_LIST};
   Buf = Buf->IgnoreParenImpCasts();
   Size = Size->IgnoreParenImpCasts();
   if (auto *MCEPtr = dyn_cast<CXXMemberCallExpr>(Buf))
@@ -1826,9 +1856,8 @@ class DataInvocationGadget : public WarningGadget {
     auto *method = cast<CXXMethodDecl>(callee);
     if (method->getNameAsString() == "data" &&
         method->getParent()->isInStdNamespace() &&
-        (method->getParent()->getName() == "span" ||
-         method->getParent()->getName() == "array" ||
-         method->getParent()->getName() == "vector"))
+        llvm::is_contained({SIZED_CONTAINER_OR_VIEW_LIST},
+                           method->getParent()->getName()))
       return true;
     return false;
   }
diff --git 
a/clang/test/SemaCXX/warn-unsafe-buffer-usage-in-container-span-construct.cpp 
b/clang/test/SemaCXX/warn-unsafe-buffer-usage-in-container-span-construct.cpp
index e287f00a5453b..1e7855517207e 100644
--- 
a/clang/test/SemaCXX/warn-unsafe-buffer-usage-in-container-span-construct.cpp
+++ 
b/clang/test/SemaCXX/warn-unsafe-buffer-usage-in-container-span-construct.cpp
@@ -1,5 +1,7 @@
 // RUN: %clang_cc1 -std=c++20  -Wno-all  -Wunsafe-buffer-usage-in-container 
-verify %s
 
+typedef unsigned int size_t;
+
 namespace std {
   template <class T> class span {
   public:
@@ -16,6 +18,9 @@ namespace std {
 
     template<class R>
     constexpr span(R && range){};
+
+    T* begin() noexcept;
+    T* end() noexcept;
   };
 
 
@@ -27,6 +32,37 @@ namespace std {
     return &__x;
   }
 
+  template <typename T, size_t N>
+  struct array {
+    T* begin() noexcept;
+    const T* begin() const noexcept;
+    T* end() noexcept;
+    const T* end() const noexcept;
+    size_t size() const noexcept;
+    T * data() const noexcept;
+    T& operator[](size_t n);
+  };
+
+  template<class T>
+  class initializer_list {
+  public:
+    size_t size() const noexcept;
+    const T* begin() const noexcept;
+    const T* end() const noexcept;
+    T * data() const noexcept;
+  };
+
+  template<typename T>
+  struct basic_string {
+    T *c_str() const noexcept;
+    T *data()  const noexcept;
+    unsigned size();
+    const T* begin() const noexcept;
+    const T* end() const noexcept;
+  };
+
+  typedef basic_string<char> string;
+  typedef basic_string<wchar_t> wstring;
 }
 
 namespace irrelevant_constructors {
@@ -232,3 +268,27 @@ struct HoldsStdSpanAndNotInitializedInCtor {
       : Ptr(P), Size(S)
   {}
 };
+
+namespace test_begin_end {
+  struct Object {
+    int * begin();
+    int * end();
+  };
+  void safe_cases(std::span<int> Sp, std::array<int, 10> Arr, std::string Str, 
std::initializer_list<Object> Il) {
+    std::span<int>{Sp.begin(), Sp.end()};
+    std::span<int>{Arr.begin(), Arr.end()};
+    std::span<char>{Str.begin(), Str.end()};
+    std::span<Object>{Il.begin(), Il.end()};
+  }
+
+  void unsafe_cases(std::span<int> Sp, std::array<int, 10> Arr, std::string 
Str, std::initializer_list<Object> Il,
+                   Object Obj) {
+    std::span<int>{Obj.begin(), Obj.end()}; // expected-warning {{the 
two-parameter std::span construction is unsafe as it can introduce mismatch 
between buffer size and the bound information}}
+    std::span<int>{Sp.end(), Sp.begin()};   // expected-warning {{the 
two-parameter std::span construction is unsafe as it can introduce mismatch 
between buffer size and the bound information}}
+    std::span<int>{Sp.begin(), Arr.end()};   // expected-warning {{the 
two-parameter std::span construction is unsafe as it can introduce mismatch 
between buffer size and the bound information}}
+  }
+
+  void unsupport_cases(std::array<Object, 10> Arr) {
+    std::span<int>{Arr[0].begin(), Arr[0].end()}; // expected-warning {{the 
two-parameter std::span construction is unsafe as it can introduce mismatch 
between buffer size and the bound information}}
+  }
+}

``````````

</details>


https://github.com/llvm/llvm-project/pull/145311
_______________________________________________
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits

Reply via email to