[clang] 5302d36 - [WebKit Checkers] Add built-in recognition for standard view types (#226350)
via cfe-commits
cfe-commits at lists.llvm.org
Sat Sep 26 09:52:58 PDT 2026
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 at -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 at -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 at -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 at -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 at -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 at -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 at -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 at -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 at -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 at -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 at -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 at -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 at -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 at -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 at -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 at -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 at -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 at -1{{Local variable 'p' is a loan on CanBorrow type 'Vector<char>' that is not guarded by a Borrow [alpha.webkit.UnborrowedLocalVarsChecker]}}
+ // expected-warning at -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 at -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 at -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 at -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;
}
More information about the cfe-commits
mailing list