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
