Author: geoffreygaren
Date: 2026-09-26T09:52:51-07:00
New Revision: 5302d361339066363854a4e12e16e413af9ce1ce

URL: 
https://github.com/llvm/llvm-project/commit/5302d361339066363854a4e12e16e413af9ce1ce
DIFF: 
https://github.com/llvm/llvm-project/commit/5302d361339066363854a4e12e16e413af9ce1ce.diff

LOG: [WebKit Checkers] Add built-in recognition for standard view types 
(#226350)

libc++ doesn't fully annotate `[[clang::lifetimebound]]` for all view
types. This results in false negatives in borrow checking.

Ultimately we need to fix this in libc++, but for now we can work around
the most common / most important false negatives. For example, borrow
checking can now check

    for (auto& x : vector | std::views::reverse) { ... }

Assisted-by: Claude

Added: 
    

Modified: 
    clang/docs/analyzer/checkers.md
    clang/lib/StaticAnalyzer/Checkers/WebKit/ASTUtils.cpp
    clang/lib/StaticAnalyzer/Checkers/WebKit/PtrTypesSemantics.cpp
    clang/lib/StaticAnalyzer/Checkers/WebKit/PtrTypesSemantics.h
    clang/test/Analysis/Checkers/WebKit/mock-canborrow.h
    clang/test/Analysis/Checkers/WebKit/unborrowed-call-args.cpp
    clang/test/Analysis/Checkers/WebKit/unborrowed-local-vars-cxx23.cpp
    clang/test/Analysis/Checkers/WebKit/unborrowed-local-vars.cpp

Removed: 
    


################################################################################
diff  --git a/clang/docs/analyzer/checkers.md b/clang/docs/analyzer/checkers.md
index f6b6aa3212c12..b3a64ccd564d4 100644
--- a/clang/docs/analyzer/checkers.md
+++ b/clang/docs/analyzer/checkers.md
@@ -4284,6 +4284,26 @@ The cost is that an identity function is reported even 
though its result really
 > }
 > ```
 
+Includes built-in recognition for std view types. For example:
+
+> ```cpp
+> void foo8(Vector<char>& buffer) {
+>   for (char& c : buffer | std::views::reverse) // warn
+>     someFunction();
+> }
+>
+> void foo9(Vector<char>& buffer) {
+>   // ok, C++23 extends the borrow() temporary across the loop
+>   for (char& c : borrow(buffer).get() | std::views::reverse)
+>     someFunction();
+> }
+>
+> void foo10(Vector<char>& buffer) {
+>   char* p = std::data(buffer); // warn
+>   someFunction();
+> }
+> ```
+
 #### alpha.webkit.UnborrowedCallArgsChecker
 
 The same rule as alpha.webkit.UnborrowedLocalVarsChecker, applied to function 
arguments.

diff  --git a/clang/lib/StaticAnalyzer/Checkers/WebKit/ASTUtils.cpp 
b/clang/lib/StaticAnalyzer/Checkers/WebKit/ASTUtils.cpp
index 995667225c961..a1dae480f85b2 100644
--- a/clang/lib/StaticAnalyzer/Checkers/WebKit/ASTUtils.cpp
+++ b/clang/lib/StaticAnalyzer/Checkers/WebKit/ASTUtils.cpp
@@ -38,11 +38,30 @@ static bool tryToFindPtrOriginImpl(
 
 namespace {
 
+bool isStdViewType(QualType T) {
+  return !T.isNull() &&
+         isStdView(T.getNonReferenceType()->getAsCXXRecordDecl());
+}
+
+void appendPresumedBorrowSources(
+    const FunctionDecl *Callee, ArrayRef<const Expr *> Args,
+    SmallVectorImpl<const Expr *> &LifetimeBoundArgs) {
+  for (unsigned I = 0; I < Args.size(); ++I) {
+    QualType ParamType;
+    if (Callee && I < Callee->getNumParams())
+      ParamType = Callee->getParamDecl(I)->getType();
+    QualType ArgType = Args[I]->getType();
+    if ((!ParamType.isNull() && ParamType->isReferenceType()) ||
+        (!ArgType.isNull() && isView(ArgType)))
+      LifetimeBoundArgs.push_back(Args[I]);
+  }
+}
+
 /// Collects the entries of \p Args that \p Callee declares
 /// [[clang::lifetimebound]].
 void findLifetimeBoundArgs(const FunctionDecl *Callee,
                            ArrayRef<const Expr *> Args,
-                           SmallVectorImpl<const Expr *> &BoundArgs) {
+                           SmallVectorImpl<const Expr *> &LifetimeBoundArgs) {
   if (!Callee)
     return;
   const FunctionDecl *Canon =
@@ -50,23 +69,29 @@ void findLifetimeBoundArgs(const FunctionDecl *Callee,
   unsigned Count = std::min<unsigned>(Canon->getNumParams(), Args.size());
   for (unsigned I = 0; I < Count; ++I) {
     if (Canon->getParamDecl(I)->hasAttr<LifetimeBoundAttr>())
-      BoundArgs.push_back(Args[I]);
+      LifetimeBoundArgs.push_back(Args[I]);
   }
 }
 
 /// Collects the arguments that \p Construct declares [[clang::lifetimebound]].
+/// Absent annotations, a std view constructor is treated as if libc++ had
+/// annotated it.
 void findLifetimeBoundArgs(const CXXConstructExpr *Construct,
-                           SmallVectorImpl<const Expr *> &BoundArgs) {
-  findLifetimeBoundArgs(
-      Construct->getConstructor(),
-      ArrayRef<const Expr *>(Construct->getArgs(), Construct->getNumArgs()),
-      BoundArgs);
+                           SmallVectorImpl<const Expr *> &LifetimeBoundArgs) {
+  const auto *Ctor = Construct->getConstructor();
+  ArrayRef<const Expr *> Args(Construct->getArgs(), Construct->getNumArgs());
+  findLifetimeBoundArgs(Ctor, Args, LifetimeBoundArgs);
+  if (!LifetimeBoundArgs.empty() || !Ctor || !isStdView(Ctor->getParent()))
+    return;
+  appendPresumedBorrowSources(Ctor, Args, LifetimeBoundArgs);
 }
 
 /// Collects the arguments that \p Call declares [[clang::lifetimebound]],
-/// including the implicit 'this' argument.
+/// including the implicit 'this' argument. Absent annotations, a call that
+/// returns or operates on a std view, or to std::data or std::get, is treated
+/// as if libc++ had annotated it.
 void findLifetimeBoundArgs(const CallExpr *Call,
-                           SmallVectorImpl<const Expr *> &BoundArgs) {
+                           SmallVectorImpl<const Expr *> &LifetimeBoundArgs) {
   const FunctionDecl *Callee = Call->getDirectCallee();
 
   const Expr *ObjectArg = nullptr;
@@ -77,16 +102,27 @@ void findLifetimeBoundArgs(const CallExpr *Call,
     ArgOffset = 1;
   } else if (auto *MemberCall = dyn_cast<CXXMemberCallExpr>(Call))
     ObjectArg = MemberCall->getImplicitObjectArgument();
+  ArrayRef<const Expr *> Args(Call->getArgs() + ArgOffset,
+                              Call->getNumArgs() - ArgOffset);
 
   if (auto *MD = dyn_cast_or_null<CXXMethodDecl>(Callee)) {
     if (ObjectArg && lifetimes::implicitObjectParamIsLifetimeBound(MD))
-      BoundArgs.push_back(ObjectArg);
+      LifetimeBoundArgs.push_back(ObjectArg);
   }
+  findLifetimeBoundArgs(Callee, Args, LifetimeBoundArgs);
+  if (!LifetimeBoundArgs.empty() || !Callee)
+    return;
 
-  findLifetimeBoundArgs(Callee,
-                        ArrayRef<const Expr *>(Call->getArgs() + ArgOffset,
-                                               Call->getNumArgs() - ArgOffset),
-                        BoundArgs);
+  bool IsStdAccessor =
+      Callee->isInStdNamespace() &&
+      (safeGetName(Callee) == "data" || safeGetName(Callee) == "get");
+  if (!isStdViewType(Callee->getReturnType()) &&
+      !(ObjectArg && isStdViewType(ObjectArg->getType())) && !IsStdAccessor)
+    return;
+
+  if (ObjectArg)
+    LifetimeBoundArgs.push_back(ObjectArg);
+  appendPresumedBorrowSources(Callee, Args, LifetimeBoundArgs);
 }
 
 /// Traces each of \p Args independently and requires every one to be safe.
@@ -165,20 +201,14 @@ static bool tryToFindPtrOriginImpl(
                           PtrIsLifetimeBoundToOrigin);
 
         if (FollowLifetimeBound) {
-          SmallVector<const Expr *, 2> BoundArgs;
-          findLifetimeBoundArgs(tempExpr, BoundArgs);
-          if (!BoundArgs.empty())
-            PtrIsLifetimeBoundToOrigin = true;
-          if (BoundArgs.size() == 1) {
-            E = BoundArgs.front();
-            continue;
-          }
-          if (BoundArgs.size() > 1)
+          SmallVector<const Expr *, 2> LifetimeBoundArgs;
+          findLifetimeBoundArgs(tempExpr, LifetimeBoundArgs);
+          if (!LifetimeBoundArgs.empty())
             return tryToFindPtrOriginOfEach(
-                BoundArgs, StopAtFirstRefCountedObj, isSafePtr, isSafePtrType,
-                isSafeGlobalDecl, callback,
-                OriginDependsOnFullExpressionTemporary,
-                PtrIsLifetimeBoundToOrigin);
+                LifetimeBoundArgs, StopAtFirstRefCountedObj, isSafePtr,
+                isSafePtrType, isSafeGlobalDecl, callback,
+                /*OriginDependsOnFullExpressionTemporary=*/false,
+                /*PtrIsLifetimeBoundToOrigin=*/true);
         }
         break;
       }
@@ -370,20 +400,14 @@ static bool tryToFindPtrOriginImpl(
       }
 
       if (FollowLifetimeBound) {
-        SmallVector<const Expr *, 2> BoundArgs;
-        findLifetimeBoundArgs(call, BoundArgs);
-        if (!BoundArgs.empty())
-          PtrIsLifetimeBoundToOrigin = true;
-        if (BoundArgs.size() == 1) {
-          E = BoundArgs.front();
-          continue;
-        }
-        if (BoundArgs.size() > 1)
+        SmallVector<const Expr *, 2> LifetimeBoundArgs;
+        findLifetimeBoundArgs(call, LifetimeBoundArgs);
+        if (!LifetimeBoundArgs.empty())
           return tryToFindPtrOriginOfEach(
-              BoundArgs, StopAtFirstRefCountedObj, isSafePtr, isSafePtrType,
-              isSafeGlobalDecl, callback,
-              OriginDependsOnFullExpressionTemporary,
-              PtrIsLifetimeBoundToOrigin);
+              LifetimeBoundArgs, StopAtFirstRefCountedObj, isSafePtr,
+              isSafePtrType, isSafeGlobalDecl, callback,
+              /*OriginDependsOnFullExpressionTemporary=*/false,
+              /*PtrIsLifetimeBoundToOrigin=*/true);
       }
     }
     if (auto *ObjCMsgExpr = dyn_cast<ObjCMessageExpr>(E)) {

diff  --git a/clang/lib/StaticAnalyzer/Checkers/WebKit/PtrTypesSemantics.cpp 
b/clang/lib/StaticAnalyzer/Checkers/WebKit/PtrTypesSemantics.cpp
index b38bbba2c173f..a89c5b432bd75 100644
--- a/clang/lib/StaticAnalyzer/Checkers/WebKit/PtrTypesSemantics.cpp
+++ b/clang/lib/StaticAnalyzer/Checkers/WebKit/PtrTypesSemantics.cpp
@@ -17,6 +17,7 @@
 #include "clang/AST/StmtVisitor.h"
 #include "clang/Analysis/Analyses/LifetimeSafety/LifetimeAnnotations.h"
 #include "clang/Analysis/DomainSpecific/CocoaConventions.h"
+#include "llvm/ADT/StringSet.h"
 #include <optional>
 
 using namespace clang;
@@ -183,12 +184,56 @@ static bool hasLifetimeBoundCtor(const 
clang::CXXRecordDecl *R) {
   return false;
 }
 
+static bool isStdRangesViewInterface(const clang::CXXRecordDecl *R) {
+  if (!R || !R->getIdentifier() || R->getName() != "view_interface")
+    return false;
+  const auto *NS = dyn_cast<NamespaceDecl>(R->getDeclContext());
+  return NS && NS->getIdentifier() && NS->getName() == "ranges" &&
+         NS->getParent()->isStdNamespace();
+}
+
+static bool derivesFromViewInterface(const clang::CXXRecordDecl *R) {
+  if (!R)
+    return false;
+  R = R->getDefinition();
+  if (!R)
+    return false;
+  if (isStdRangesViewInterface(R))
+    return true;
+  for (const CXXBaseSpecifier &Base : R->bases()) {
+    if (derivesFromViewInterface(Base.getType()->getAsCXXRecordDecl()))
+      return true;
+  }
+  return false;
+}
+
+bool isStdView(const clang::CXXRecordDecl *R) {
+  if (!R)
+    return false;
+  if (R->hasAttr<PointerAttr>())
+    return true;
+  static const llvm::StringSet<> StdIterators{
+      "reverse_iterator", "move_iterator", "common_iterator",
+      "counted_iterator", "basic_const_iterator"};
+  if (R->isInStdNamespace() && R->getIdentifier() &&
+      StdIterators.contains(R->getName()))
+    return true;
+  if (derivesFromViewInterface(R))
+    return true;
+  if (const auto *Parent = dyn_cast<CXXRecordDecl>(R->getDeclContext()))
+    return isStdView(Parent);
+  return false;
+}
+
 bool isView(const clang::QualType T) {
   if (T->isReferenceType())
     return true;
   if (lifetimes::isPointerLikeType(T))
     return true;
-  return hasLifetimeBoundCtor(T->getAsCXXRecordDecl());
+  auto *Record = T->getAsCXXRecordDecl();
+  if (isStdView(Record))
+    return true;
+  return hasLifetimeBoundCtor(Record);
 }
 
 bool isRefType(const std::string &Name) {

diff  --git a/clang/lib/StaticAnalyzer/Checkers/WebKit/PtrTypesSemantics.h 
b/clang/lib/StaticAnalyzer/Checkers/WebKit/PtrTypesSemantics.h
index 9e9bb995f7ca2..7fb78233a6288 100644
--- a/clang/lib/StaticAnalyzer/Checkers/WebKit/PtrTypesSemantics.h
+++ b/clang/lib/StaticAnalyzer/Checkers/WebKit/PtrTypesSemantics.h
@@ -73,6 +73,13 @@ clang::QualType borrowedType(clang::QualType T);
 /// \returns true if a value of type \p T is a pointer/reference/view.
 bool isView(const clang::QualType T);
 
+/// \returns true if \p Class declares reference semantics structurally: it is
+/// annotated [[gsl::Pointer]] (explicitly, or by Sema's inference for
+/// standard types), derives from std::ranges::view_interface, is a standard
+/// iterator adaptor, or is nested inside such a class, as the iterators of
+/// standard views are.
+bool isStdView(const clang::CXXRecordDecl *Class);
+
 /// \returns true if \p Class is ref-counted, false if not.
 bool isRefCounted(const clang::CXXRecordDecl *Class);
 

diff  --git a/clang/test/Analysis/Checkers/WebKit/mock-canborrow.h 
b/clang/test/Analysis/Checkers/WebKit/mock-canborrow.h
index 7b68bba4999a3..fc756714dc4c1 100644
--- a/clang/test/Analysis/Checkers/WebKit/mock-canborrow.h
+++ b/clang/test/Analysis/Checkers/WebKit/mock-canborrow.h
@@ -255,4 +255,34 @@ class Function {
 void callEscaping(const Function &);
 void callNoEscape([[clang::noescape]] const Function &);
 
+namespace std {
+inline namespace __1 {
+using size_t = decltype(sizeof(0));
+
+namespace ranges {
+template <typename Derived> class view_interface {};
+} // namespace ranges
+
+template <typename Iterator> class reverse_iterator {
+public:
+  reverse_iterator(Iterator);
+  auto &operator*() const { return *m_it; }
+  reverse_iterator &operator++();
+  bool operator!=(const reverse_iterator &) const;
+
+private:
+  Iterator m_it;
+};
+
+template <typename A, typename B> struct pair {
+  A first;
+  B second;
+};
+
+template <size_t I, typename A, typename B> A &get(pair<A, B> &);
+
+template <typename T> T *data(Vector<T> &);
+} // namespace __1
+} // namespace std
+
 #endif

diff  --git a/clang/test/Analysis/Checkers/WebKit/unborrowed-call-args.cpp 
b/clang/test/Analysis/Checkers/WebKit/unborrowed-call-args.cpp
index 72cd785725b41..5de9efce09423 100644
--- a/clang/test/Analysis/Checkers/WebKit/unborrowed-call-args.cpp
+++ b/clang/test/Analysis/Checkers/WebKit/unborrowed-call-args.cpp
@@ -192,6 +192,7 @@ namespace known_gaps {
 void unannotated_intermediate() {
   Vector<char> vec;
   takeSpan(makeSpanUnannotated(vec));
+  // expected-warning@-1{{Function argument 'makeSpanUnannotated(vec)' (to 
'takeSpan') is a loan on CanBorrow type 'Vector<char>' that is not guarded by a 
Borrow [alpha.webkit.UnborrowedCallArgsChecker]}}
 }
 
 inline void trivialSink(char &c) {}

diff  --git 
a/clang/test/Analysis/Checkers/WebKit/unborrowed-local-vars-cxx23.cpp 
b/clang/test/Analysis/Checkers/WebKit/unborrowed-local-vars-cxx23.cpp
index 0f1ae4e58d6ac..3fc233b0c0abb 100644
--- a/clang/test/Analysis/Checkers/WebKit/unborrowed-local-vars-cxx23.cpp
+++ b/clang/test/Analysis/Checkers/WebKit/unborrowed-local-vars-cxx23.cpp
@@ -11,24 +11,249 @@ void borrow_function_get_loop(Vector<char> &vec) {
   }
 }
 
-struct ReversedChars {
-  char *b;
-  char *e;
-  char *begin() const;
-  char *end() const;
-};
 struct ReverseAdaptor {};
-ReversedChars operator|(Vector<char> &vec, ReverseAdaptor);
+struct ReversedChars : std::ranges::view_interface<ReversedChars> {
+  explicit ReversedChars(Vector<char> &);
+  struct Iterator {
+    char &operator*() const;
+    Iterator &operator++();
+    bool operator!=(const Iterator &) const;
+  };
+  Iterator begin() const;
+  Iterator end() const;
+  ReversedChars zipWith(Vector<int> &) const;
+};
+inline constexpr ReverseAdaptor reversed{};
+ReversedChars operator|(Vector<char> &, const ReverseAdaptor &);
+ReversedChars operator|(ReversedChars &&, const ReverseAdaptor &);
 
-void borrow_get_pipe_loop(Vector<char> &vec) {
-  for (char &c : borrow(vec).get() | ReverseAdaptor()) {
+void unguarded_global_adaptor_pipe_loop(Vector<char> &vec) {
+  for (char &c : vec | reversed) {
+    // expected-warning@-1{{Local variable 'c' is a loan on CanBorrow type 
'Vector<char>' that is not guarded by a Borrow 
[alpha.webkit.UnborrowedLocalVarsChecker]}}
+    someFunction();
+    (void)c;
+  }
+}
+
+void borrowed_global_adaptor_pipe_loop(Vector<char> &vec) {
+  for (char &c : borrow(vec).get() | reversed) {
     someFunction();
     (void)c;
   }
 }
 
+void chained_pipe_unguarded(Vector<char> &vec) {
+  ReversedChars rv = vec | reversed | reversed;
+  // expected-warning@-1{{Local variable 'rv' is a loan on CanBorrow type 
'Vector<char>' that is not guarded by a Borrow 
[alpha.webkit.UnborrowedLocalVarsChecker]}}
+  someFunction();
+  (void)rv;
+}
+
+void chained_pipe_borrowed(Vector<char> &vec) {
+  Borrow<Vector<char>> b(vec);
+  ReversedChars rv = b.get() | reversed | reversed;
+  someFunction();
+  (void)rv;
+}
+
 void unguarded_pipe_loop(Vector<char> &vec) {
   for (char &c : vec | ReverseAdaptor()) {
+    // expected-warning@-1{{Local variable 'c' is a loan on CanBorrow type 
'Vector<char>' that is not guarded by a Borrow 
[alpha.webkit.UnborrowedLocalVarsChecker]}}
+    someFunction();
+    (void)c;
+  }
+}
+
+void borrow_get_pipe_loop(Vector<char> &vec) {
+  for (char &c : borrow(vec).get() | ReverseAdaptor()) {
+    someFunction();
+    (void)c;
+  }
+}
+
+void named_view(Vector<char> &vec) {
+  ReversedChars rv = vec | ReverseAdaptor();
+  // expected-warning@-1{{Local variable 'rv' is a loan on CanBorrow type 
'Vector<char>' that is not guarded by a Borrow 
[alpha.webkit.UnborrowedLocalVarsChecker]}}
+  someFunction();
+  (void)rv;
+}
+
+void named_view_borrowed(Vector<char> &vec) {
+  Borrow<Vector<char>> b(vec);
+  ReversedChars rv = b.get() | ReverseAdaptor();
+  someFunction();
+  (void)rv;
+}
+
+void constructed_view(Vector<char> &vec) {
+  ReversedChars rv(vec);
+  // expected-warning@-1{{Local variable 'rv' is a loan on CanBorrow type 
'Vector<char>' that is not guarded by a Borrow 
[alpha.webkit.UnborrowedLocalVarsChecker]}}
+  someFunction();
+  (void)rv;
+}
+
+struct PlainReversed : std::ranges::view_interface<PlainReversed> {
+  explicit PlainReversed(Vector<char> &);
+  std::reverse_iterator<char *> begin() const;
+  std::reverse_iterator<char *> end() const;
+};
+
+void std_reverse_iterator_loop(Vector<char> &vec) {
+  for (char &c : PlainReversed(vec)) {
+    // expected-warning@-1{{Local variable 'c' is a loan on CanBorrow type 
'Vector<char>' that is not guarded by a Borrow 
[alpha.webkit.UnborrowedLocalVarsChecker]}}
+    someFunction();
+    (void)c;
+  }
+}
+
+void std_reverse_iterator_borrowed(Vector<char> &vec) {
+  Borrow<Vector<char>> b(vec);
+  for (char &c : PlainReversed(b.get())) {
+    someFunction();
+    (void)c;
+  }
+}
+
+void data_from_vector(Vector<char> &vec) {
+  char *p = std::data(vec);
+  // expected-warning@-1{{Local variable 'p' is a loan on CanBorrow type 
'Vector<char>' that is not guarded by a Borrow 
[alpha.webkit.UnborrowedLocalVarsChecker]}}
+  someFunction();
+  (void)p;
+}
+
+void data_from_borrow(Vector<char> &vec) {
+  Borrow<Vector<char>> b(vec);
+  char *p = std::data(b.get());
+  someFunction();
+  (void)p;
+}
+
+void get_from_element(Vector<std::pair<int, int>> &vec) {
+  auto &first = std::get<0>(vec[0]);
+  // expected-warning@-1{{Local variable 'first' is a loan on CanBorrow type 
'Vector<std::pair<int, int>>' that is not guarded by a Borrow 
[alpha.webkit.UnborrowedLocalVarsChecker]}}
+  someFunction();
+  (void)first;
+}
+
+void get_from_borrowed_element(Vector<std::pair<int, int>> &vec) {
+  Borrow<Vector<std::pair<int, int>>> b(vec);
+  auto &first = std::get<0>(b.get()[0]);
+  someFunction();
+  (void)first;
+}
+
+struct AnnotatedView : std::ranges::view_interface<AnnotatedView> {
+  AnnotatedView(Vector<char> &tracked LIFETIME_BOUND, Vector<char> &untracked);
+};
+
+void trusted_annotations(Vector<char> &tracked, Vector<char> &untracked) {
+  Borrow<Vector<char>> b(tracked);
+  AnnotatedView v(b.get(), untracked);
+  someFunction();
+  (void)v;
+}
+
+void member_arg_unguarded(Vector<char> &vec, Vector<int> &ints) {
+  Borrow<Vector<char>> b(vec);
+  ReversedChars rv = (b.get() | reversed).zipWith(ints);
+  // expected-warning@-1{{Local variable 'rv' is a loan on CanBorrow type 
'Vector<int>' that is not guarded by a Borrow 
[alpha.webkit.UnborrowedLocalVarsChecker]}}
+  someFunction();
+  (void)rv;
+}
+
+void member_object_unguarded(Vector<char> &vec, Vector<int> &ints) {
+  Borrow<Vector<int>> b(ints);
+  ReversedChars rv = (vec | reversed).zipWith(b.get());
+  // expected-warning@-1{{Local variable 'rv' is a loan on CanBorrow type 
'Vector<char>' that is not guarded by a Borrow 
[alpha.webkit.UnborrowedLocalVarsChecker]}}
+  someFunction();
+  (void)rv;
+}
+
+void member_both_borrowed(Vector<char> &vec, Vector<int> &ints) {
+  Borrow<Vector<char>> bc(vec);
+  Borrow<Vector<int>> bi(ints);
+  ReversedChars rv = (bc.get() | reversed).zipWith(bi.get());
+  someFunction();
+  (void)rv;
+}
+
+struct ZipView : std::ranges::view_interface<ZipView> {
+  ZipView(Vector<char> &, Vector<int> &);
+  struct Iterator {
+    char &operator*() const;
+    Iterator &operator++();
+    bool operator!=(const Iterator &) const;
+  };
+  Iterator begin() const;
+  Iterator end() const;
+};
+struct ZipAdaptor {
+  ZipView operator()(Vector<char> &, Vector<int> &) const;
+};
+inline constexpr ZipAdaptor zip{};
+
+void zip_unguarded(Vector<char> &vec, Vector<int> &ints) {
+  for (char &c : zip(vec, ints)) {
+    // expected-warning@-1{{Local variable 'c' is a loan on CanBorrow type 
'Vector<char>' that is not guarded by a Borrow 
[alpha.webkit.UnborrowedLocalVarsChecker]}}
+    someFunction();
+    (void)c;
+  }
+}
+
+void zip_first_borrowed(Vector<char> &vec, Vector<int> &ints) {
+  Borrow<Vector<char>> b(vec);
+  for (char &c : zip(b.get(), ints)) {
+    // expected-warning@-1{{Local variable 'c' is a loan on CanBorrow type 
'Vector<int>' that is not guarded by a Borrow 
[alpha.webkit.UnborrowedLocalVarsChecker]}}
+    someFunction();
+    (void)c;
+  }
+}
+
+void zip_second_borrowed(Vector<char> &vec, Vector<int> &ints) {
+  Borrow<Vector<int>> b(ints);
+  for (char &c : zip(vec, b.get())) {
+    // expected-warning@-1{{Local variable 'c' is a loan on CanBorrow type 
'Vector<char>' that is not guarded by a Borrow 
[alpha.webkit.UnborrowedLocalVarsChecker]}}
+    someFunction();
+    (void)c;
+  }
+}
+
+void zip_both_borrowed(Vector<char> &vec, Vector<int> &ints) {
+  Borrow<Vector<char>> bc(vec);
+  Borrow<Vector<int>> bi(ints);
+  for (char &c : zip(bc.get(), bi.get())) {
+    someFunction();
+    (void)c;
+  }
+}
+
+void zip_constructed_second_borrowed(Vector<char> &vec, Vector<int> &ints) {
+  Borrow<Vector<int>> b(ints);
+  ZipView z(vec, b.get());
+  // expected-warning@-1{{Local variable 'z' is a loan on CanBorrow type 
'Vector<char>' that is not guarded by a Borrow 
[alpha.webkit.UnborrowedLocalVarsChecker]}}
+  someFunction();
+  (void)z;
+}
+
+void zip_constructed_both_borrowed(Vector<char> &vec, Vector<int> &ints) {
+  Borrow<Vector<char>> bc(vec);
+  Borrow<Vector<int>> bi(ints);
+  ZipView z(bc.get(), bi.get());
+  someFunction();
+  (void)z;
+}
+
+struct NonStdAdaptor {};
+struct NonStdReversed {
+  char *b;
+  char *e;
+  char *begin() const;
+  char *end() const;
+};
+NonStdReversed operator|(Vector<char> &, NonStdAdaptor);
+
+void non_std_pipe_loop(Vector<char> &vec) {
+  for (char &c : vec | NonStdAdaptor()) {
     someFunction();
     (void)c;
   }

diff  --git a/clang/test/Analysis/Checkers/WebKit/unborrowed-local-vars.cpp 
b/clang/test/Analysis/Checkers/WebKit/unborrowed-local-vars.cpp
index 8de699f967146..ac2c7dc082e97 100644
--- a/clang/test/Analysis/Checkers/WebKit/unborrowed-local-vars.cpp
+++ b/clang/test/Analysis/Checkers/WebKit/unborrowed-local-vars.cpp
@@ -368,15 +368,60 @@ void guarded_store_through_out_pointer(Vector<char> &vec, 
char **out) {
 }
 } // namespace escape_paths
 
+struct OwningBuffer : CanBorrow {
+  char *data() LIFETIME_BOUND;
+};
+OwningBuffer makeOwningBuffer(const Vector<char> &vec LIFETIME_BOUND);
+extern const Vector<char> globalVec;
+
+void owning_temporary_from_global() {
+  char *p = makeOwningBuffer(globalVec).data();
+  // expected-warning@-1{{temporary whose address is used as value of local 
variable 'p' will be destroyed at the end of the full-expression}}
+  someFunction();
+  (void)p;
+}
+
+void owning_temporary_from_borrow(Vector<char> &vec) {
+  Borrow<Vector<char>> b(vec);
+  char *p = makeOwningBuffer(b.get()).data();
+  // expected-warning@-1{{temporary whose address is used as value of local 
variable 'p' will be destroyed at the end of the full-expression}}
+  someFunction();
+  (void)p;
+}
+
+void owning_temporary_from_unguarded(Vector<char> &vec) {
+  char *p = makeOwningBuffer(vec).data();
+  // expected-warning@-1{{Local variable 'p' is a loan on CanBorrow type 
'Vector<char>' that is not guarded by a Borrow 
[alpha.webkit.UnborrowedLocalVarsChecker]}}
+  // expected-warning@-2{{temporary whose address is used as value of local 
variable 'p' will be destroyed at the end of the full-expression}}
+  someFunction();
+  (void)p;
+}
+
+void extended_owning_from_unguarded(Vector<char> &vec) {
+  const OwningBuffer &buf = makeOwningBuffer(vec);
+  // expected-warning@-1{{Local variable 'buf' is a loan on CanBorrow type 
'Vector<char>' that is not guarded by a Borrow 
[alpha.webkit.UnborrowedLocalVarsChecker]}}
+  someFunction();
+  (void)buf;
+}
+
+void extended_owning_from_borrow(Vector<char> &vec) {
+  Borrow<Vector<char>> b(vec);
+  const OwningBuffer &buf = makeOwningBuffer(b.get());
+  someFunction();
+  (void)buf;
+}
+
 namespace known_gaps {
 void unannotated_view_constructor(Vector<char> &vec) {
   CharSpan s(vec.data());
+  // expected-warning@-1{{Local variable 's' is a loan on CanBorrow type 
'Vector<char>' that is not guarded by a Borrow 
[alpha.webkit.UnborrowedLocalVarsChecker]}}
   someFunction();
   (void)s;
 }
 
 void unannotated_function_parameter(Vector<char> &vec) {
   CharSpan s = makeSpanUnannotated(vec);
+  // expected-warning@-1{{Local variable 's' is a loan on CanBorrow type 
'Vector<char>' that is not guarded by a Borrow 
[alpha.webkit.UnborrowedLocalVarsChecker]}}
   someFunction();
   (void)s;
 }


        
_______________________________________________
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits

Reply via email to