Author: geoffreygaren
Date: 2026-09-22T22:27:28-07:00
New Revision: 081cc091b90248c62607c34eff91e7afbf252129

URL: 
https://github.com/llvm/llvm-project/commit/081cc091b90248c62607c34eff91e7afbf252129
DIFF: 
https://github.com/llvm/llvm-project/commit/081cc091b90248c62607c34eff91e7afbf252129.diff

LOG: [WebKit Checkers] Add alpha.webkit.UnborrowedLocalVarsChecker (#225274)

This is a new checker that requires a Borrow<T> when using a
pointer/reference/view that is lifetimebound to a CanBorrow type.

A CanBorrow type is a type that tracks views into its interior at
runtime, and calls crashIfBorrowed() when it invalidates such views.
Vector is the motivating example.

A design description is available at:
https://github.com/WebKit/WebKit/wiki/SaferCPP:-Borrowed-Pointer-Use-After-Destruction

I implemented UnborrowedLocalVarsChecker in terms of the existing
alpha.webkit.*LocalVarsChecker system because the requirement to hold an
overlooking smart-pointer-like-thing is pretty similar.

In some cases where the new checker is stricter than the existing
checker, this patch conditionalizes the strictness. The plan is to
upgrade existing checkers in a follow-up patch.

Assisted-by: Claude

Added: 
    clang/test/Analysis/Checkers/WebKit/mock-canborrow.h
    clang/test/Analysis/Checkers/WebKit/unborrowed-local-vars-cxx23.cpp
    clang/test/Analysis/Checkers/WebKit/unborrowed-local-vars.cpp

Modified: 
    clang/docs/analyzer/checkers.md
    clang/include/clang/StaticAnalyzer/Checkers/Checkers.td
    clang/lib/StaticAnalyzer/Checkers/WebKit/ASTUtils.cpp
    clang/lib/StaticAnalyzer/Checkers/WebKit/ASTUtils.h
    clang/lib/StaticAnalyzer/Checkers/WebKit/PtrTypesSemantics.cpp
    clang/lib/StaticAnalyzer/Checkers/WebKit/PtrTypesSemantics.h
    clang/lib/StaticAnalyzer/Checkers/WebKit/RawPtrRefCallArgsChecker.cpp
    clang/lib/StaticAnalyzer/Checkers/WebKit/RawPtrRefLocalVarsChecker.cpp
    clang/lib/StaticAnalyzer/Checkers/WebKit/RawPtrRefSafetyModel.cpp
    clang/lib/StaticAnalyzer/Checkers/WebKit/RawPtrRefSafetyModel.h
    clang/test/Analysis/Checkers/WebKit/uncounted-local-vars.cpp

Removed: 
    


################################################################################
diff  --git a/clang/docs/analyzer/checkers.md b/clang/docs/analyzer/checkers.md
index e81aa07b59ae4b..c598bf05031e99 100644
--- a/clang/docs/analyzer/checkers.md
+++ b/clang/docs/analyzer/checkers.md
@@ -4194,6 +4194,96 @@ Here are some examples of situations that we warn about 
as they *might* be poten
 > }
 > ```
 
+#### alpha.webkit.UnborrowedLocalVarsChecker
+
+A *CanBorrow* type tracks views into its interior at runtime. It calls 
`crashIfBorrowed()` in methods that destroy its interior. `WTF::Vector` is a 
motivating example: `append()` calls `crashIfBorrowed()`.
+
+`Borrow<T>` is an RAII object that tracks a view into a `CanBorrow` type: 
while a `Borrow<T>` on a `CanBorrow` object is in scope, views into the object 
remain valid (otherwise the program crashes).
+
+The goal of this rule is to require a pointer/reference/view into a 
`CanBorrow` type to be guarded by an overlooking `Borrow<T>`.
+
+These are examples do not warn:
+
+> ```cpp
+> void foo1(Vector<char>& buffer) {
+>   Borrow<Vector<char>> borrowed(buffer);
+>   char& c = borrowed.get()[0]; // ok, the loan is reached through a Borrow
+> }
+>
+> void foo2(Vector<char>& buffer) {
+>   Vector<char>& alias = buffer; // ok, names the object rather than its 
interior
+>   Vector<char>* p = &buffer;    // ok, same
+> }
+>
+> void foo3(Vector<char>& buffer) {
+>   char c = buffer[0]; // ok, a copy of an element is not a loan
+> }
+>
+> void foo4(Vector<char>& buffer) {
+>   // ok, every loan is bound to the Borrow temporary, which C++23 extends
+>   // across the loop
+>   for (char& c : borrow(buffer).get()) { }
+> }
+> ```
+
+These are examples warn:
+
+> ```cpp
+> void foo1(Vector<char>& buffer) {
+>   char& c = buffer[0];    // warn
+>   buffer.append('x');     // this would invalidate c without a crash
+> }
+>
+> void foo2(Vector<char>& buffer) {
+>   char* data = buffer.data(); // warn
+>   someFunction();             // this might invalidate data without a crash
+> }
+>
+> void foo3(Vector<Vector<char>>& outer) {
+>   Vector<char>& inner = outer[0]; // warn
+>   someFunction();                 // this might invalidate inner without a 
crash
+> }
+>
+> void foo4(Vector<char>& buffer) {
+>   for (char& c : buffer) { // warn
+>     someFunction();        // this might invalidate c without a crash
+>   }
+> }
+>
+> void foo5() {
+>   // warn: the temporary lives across the loop, but an iterator can hold a
+>   // pointer back to it, so its interior can still be destroyed. Bind it to
+>   // a name and borrow it instead.
+>   for (char& c : makeVector()) {
+>     someFunction();
+>   }
+> }
+>
+> class Node : public CanBorrow {   // owns a Vector<Node> of children
+> public:
+>   Node& firstChild() [[clang::lifetimebound]];
+>   void appendChild();
+> };
+>
+> void foo6(Node& node) {
+>   Node& child = node.firstChild(); // warn
+>   node.appendChild();              // this would invalidate child without a 
crash
+> }
+> ```
+
+A value counts as a loan when it reaches its origin through a 
`[[clang::lifetimebound]]` edge. That attribute does not distinguish a view 
into an object's interior from another name for the object itself. `foo6` shows 
why the checker resolves that ambiguity toward reporting.
+
+The cost is that an identity function is reported even though its result 
really is an alias:
+
+> ```cpp
+> Vector<char>& identity(Vector<char>& v [[clang::lifetimebound]]);
+>
+> void foo7(Vector<char>& buffer) {
+>   Vector<char>& alias = identity(buffer); // warn, although this is an alias
+>   someFunction();
+> }
+> ```
+
 #### webkit.RetainPtrCtorAdoptChecker
 
 The goal of this rule is to make sure the constructors of RetainPtr and 
OSObjectPtr as well as adoptNS, adoptCF, and adoptOSObject are used correctly.

diff  --git a/clang/include/clang/StaticAnalyzer/Checkers/Checkers.td 
b/clang/include/clang/StaticAnalyzer/Checkers/Checkers.td
index 1f2b235f9072bf..24abc8d4a4b8a4 100644
--- a/clang/include/clang/StaticAnalyzer/Checkers/Checkers.td
+++ b/clang/include/clang/StaticAnalyzer/Checkers/Checkers.td
@@ -1802,6 +1802,10 @@ def UnretainedLocalVarsChecker : 
Checker<"UnretainedLocalVarsChecker">,
   HelpText<"Check unretained local variables.">,
   Documentation<HasDocumentation>;
 
+def UnborrowedLocalVarsChecker : Checker<"UnborrowedLocalVarsChecker">,
+  HelpText<"Check local variables holding a loan on a CanBorrow object that is 
not guarded by a Borrow.">,
+  Documentation<HasDocumentation>;
+
 def RetainPtrCtorAdoptChecker : Checker<"RetainPtrCtorAdoptChecker">,
   HelpText<"Check for correct use of RetainPtr/OSObjectPtr constructor, 
adoptNS, adoptCF, and adoptOSObject">,
   Documentation<HasDocumentation>;

diff  --git a/clang/lib/StaticAnalyzer/Checkers/WebKit/ASTUtils.cpp 
b/clang/lib/StaticAnalyzer/Checkers/WebKit/ASTUtils.cpp
index 2717ccfe53fee9..6ff56e9c1ce3b4 100644
--- a/clang/lib/StaticAnalyzer/Checkers/WebKit/ASTUtils.cpp
+++ b/clang/lib/StaticAnalyzer/Checkers/WebKit/ASTUtils.cpp
@@ -14,6 +14,7 @@
 #include "clang/AST/ExprCXX.h"
 #include "clang/AST/ExprObjC.h"
 #include "clang/AST/StmtVisitor.h"
+#include "clang/Analysis/Analyses/LifetimeSafety/LifetimeAnnotations.h"
 #include <optional>
 #include <utility>
 
@@ -24,14 +25,102 @@ bool isSafePtr(clang::CXXRecordDecl *Decl) {
 }
 
 static bool tryToFindPtrOriginImpl(
-    const Expr *E, bool StopAtFirstRefCountedObj,
+    const Expr *E, bool StopAtFirstRefCountedObj, bool FollowLifetimeBound,
     std::function<bool(const clang::CXXRecordDecl *)> isSafePtr,
     std::function<bool(const clang::QualType)> isSafePtrType,
     std::function<bool(const clang::Decl *)> isSafeGlobalDecl,
     std::function<bool(const clang::Expr *, bool /*IsSafe*/,
-                       bool /*OriginDependsOnFullExpressionTemporary*/)>
+                       bool /*OriginDependsOnFullExpressionTemporary*/,
+                       bool /*PtrIsLifetimeBoundToOrigin*/)>
         callback,
-    bool OriginDependsOnFullExpressionTemporary) {
+    bool OriginDependsOnFullExpressionTemporary,
+    bool PtrIsLifetimeBoundToOrigin);
+
+namespace {
+
+/// 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) {
+  if (!Callee)
+    return;
+  const FunctionDecl *Canon =
+      lifetimes::getDeclWithMergedLifetimeBoundAttrs(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]);
+  }
+}
+
+/// Collects the arguments that \p Construct declares [[clang::lifetimebound]].
+void findLifetimeBoundArgs(const CXXConstructExpr *Construct,
+                           SmallVectorImpl<const Expr *> &BoundArgs) {
+  findLifetimeBoundArgs(
+      Construct->getConstructor(),
+      ArrayRef<const Expr *>(Construct->getArgs(), Construct->getNumArgs()),
+      BoundArgs);
+}
+
+/// Collects the arguments that \p Call declares [[clang::lifetimebound]],
+/// including the implicit 'this' argument.
+void findLifetimeBoundArgs(const CallExpr *Call,
+                           SmallVectorImpl<const Expr *> &BoundArgs) {
+  const FunctionDecl *Callee = Call->getDirectCallee();
+
+  const Expr *ObjectArg = nullptr;
+  unsigned ArgOffset = 0;
+  if (isa<CXXOperatorCallExpr>(Call) && Callee &&
+      Callee->isCXXInstanceMember() && Call->getNumArgs()) {
+    ObjectArg = Call->getArg(0);
+    ArgOffset = 1;
+  } else if (auto *MemberCall = dyn_cast<CXXMemberCallExpr>(Call))
+    ObjectArg = MemberCall->getImplicitObjectArgument();
+
+  if (auto *MD = dyn_cast_or_null<CXXMethodDecl>(Callee)) {
+    if (ObjectArg && lifetimes::implicitObjectParamIsLifetimeBound(MD))
+      BoundArgs.push_back(ObjectArg);
+  }
+
+  findLifetimeBoundArgs(Callee,
+                        ArrayRef<const Expr *>(Call->getArgs() + ArgOffset,
+                                               Call->getNumArgs() - ArgOffset),
+                        BoundArgs);
+}
+
+/// Traces each of \p Args independently and requires every one to be safe.
+bool tryToFindPtrOriginOfEach(
+    ArrayRef<const Expr *> Args, bool StopAtFirstRefCountedObj,
+    const std::function<bool(const clang::CXXRecordDecl *)> &isSafePtr,
+    const std::function<bool(const clang::QualType)> &isSafePtrType,
+    const std::function<bool(const clang::Decl *)> &isSafeGlobalDecl,
+    const std::function<bool(const clang::Expr *, bool, bool, bool)> &callback,
+    bool OriginDependsOnFullExpressionTemporary,
+    bool PtrIsLifetimeBoundToOrigin) {
+  for (const Expr *Arg : Args) {
+    if (!tryToFindPtrOriginImpl(
+            Arg, StopAtFirstRefCountedObj, /*FollowLifetimeBound=*/true,
+            isSafePtr, isSafePtrType, isSafeGlobalDecl, callback,
+            OriginDependsOnFullExpressionTemporary, 
PtrIsLifetimeBoundToOrigin))
+      return false;
+  }
+  return true;
+}
+
+} // namespace
+
+static bool tryToFindPtrOriginImpl(
+    const Expr *E, bool StopAtFirstRefCountedObj, bool FollowLifetimeBound,
+    std::function<bool(const clang::CXXRecordDecl *)> isSafePtr,
+    std::function<bool(const clang::QualType)> isSafePtrType,
+    std::function<bool(const clang::Decl *)> isSafeGlobalDecl,
+    std::function<bool(const clang::Expr *, bool /*IsSafe*/,
+                       bool /*OriginDependsOnFullExpressionTemporary*/,
+                       bool /*PtrIsLifetimeBoundToOrigin*/)>
+        callback,
+    bool OriginDependsOnFullExpressionTemporary,
+    bool PtrIsLifetimeBoundToOrigin) {
   while (E) {
     if (auto *DRE = dyn_cast<DeclRefExpr>(E)) {
       if (auto *VD = dyn_cast_or_null<VarDecl>(DRE->getDecl())) {
@@ -39,10 +128,19 @@ static bool tryToFindPtrOriginImpl(
         auto IsImmortal = safeGetName(VD) == "NSApp";
         if (VD->hasGlobalStorage() && (IsImmortal || QT.isConstQualified()))
           return callback(E, /*IsSafe=*/true,
-                          OriginDependsOnFullExpressionTemporary);
+                          OriginDependsOnFullExpressionTemporary,
+                          PtrIsLifetimeBoundToOrigin);
         if (VD->hasGlobalStorage() && isSafeGlobalDecl(VD))
           return callback(E, /*IsSafe=*/true,
-                          OriginDependsOnFullExpressionTemporary);
+                          OriginDependsOnFullExpressionTemporary,
+                          PtrIsLifetimeBoundToOrigin);
+
+        if (FollowLifetimeBound && VD->isImplicit() && VD->isLocalVarDecl()) {
+          if (auto *Init = VD->getInit()) {
+            E = Init;
+            continue;
+          }
+        }
       }
     }
     if (auto *Cleanups = dyn_cast<ExprWithCleanups>(E)) {
@@ -63,14 +161,33 @@ static bool tryToFindPtrOriginImpl(
       if (auto *C = tempExpr->getConstructor()) {
         if (auto *Class = C->getParent(); Class && isSafePtr(Class))
           return callback(E, /*IsSafe=*/true,
-                          OriginDependsOnFullExpressionTemporary);
+                          OriginDependsOnFullExpressionTemporary,
+                          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)
+            return tryToFindPtrOriginOfEach(
+                BoundArgs, StopAtFirstRefCountedObj, isSafePtr, isSafePtrType,
+                isSafeGlobalDecl, callback,
+                OriginDependsOnFullExpressionTemporary,
+                PtrIsLifetimeBoundToOrigin);
+        }
         break;
       }
     }
     if (auto *TempExpr = dyn_cast<CXXUnresolvedConstructExpr>(E)) {
       if (isSafePtrType(TempExpr->getTypeAsWritten()))
         return callback(TempExpr, /*IsSafe=*/true,
-                        OriginDependsOnFullExpressionTemporary);
+                        OriginDependsOnFullExpressionTemporary,
+                        PtrIsLifetimeBoundToOrigin);
     }
     if (auto *POE = dyn_cast<PseudoObjectExpr>(E)) {
       if (auto *RF = POE->getResultExpr()) {
@@ -88,13 +205,17 @@ static bool tryToFindPtrOriginImpl(
     }
     if (auto *Expr = dyn_cast<ConditionalOperator>(E)) {
       return tryToFindPtrOriginImpl(Expr->getTrueExpr(),
-                                    StopAtFirstRefCountedObj, isSafePtr,
+                                    StopAtFirstRefCountedObj,
+                                    FollowLifetimeBound, isSafePtr,
                                     isSafePtrType, isSafeGlobalDecl, callback,
-                                    OriginDependsOnFullExpressionTemporary) &&
+                                    OriginDependsOnFullExpressionTemporary,
+                                    PtrIsLifetimeBoundToOrigin) &&
              tryToFindPtrOriginImpl(Expr->getFalseExpr(),
-                                    StopAtFirstRefCountedObj, isSafePtr,
+                                    StopAtFirstRefCountedObj,
+                                    FollowLifetimeBound, isSafePtr,
                                     isSafePtrType, isSafeGlobalDecl, callback,
-                                    OriginDependsOnFullExpressionTemporary);
+                                    OriginDependsOnFullExpressionTemporary,
+                                    PtrIsLifetimeBoundToOrigin);
     }
     if (auto *cast = dyn_cast<CastExpr>(E)) {
       if (StopAtFirstRefCountedObj) {
@@ -102,11 +223,13 @@ static bool tryToFindPtrOriginImpl(
                 dyn_cast_or_null<FunctionDecl>(cast->getConversionFunction())) 
{
           if (isCtorOfSafePtr(ConversionFunc))
             return callback(E, /*IsSafe=*/true,
-                            OriginDependsOnFullExpressionTemporary);
+                            OriginDependsOnFullExpressionTemporary,
+                            PtrIsLifetimeBoundToOrigin);
         }
         if (isa<CXXFunctionalCastExpr>(E) && isSafePtrType(cast->getType()))
           return callback(E, /*IsSafe=*/true,
-                          OriginDependsOnFullExpressionTemporary);
+                          OriginDependsOnFullExpressionTemporary,
+                          PtrIsLifetimeBoundToOrigin);
       }
       // FIXME: This can give false "origin" that would lead to false negatives
       // in checkers. See https://reviews.llvm.org/D37023 for reference.
@@ -119,13 +242,15 @@ static bool tryToFindPtrOriginImpl(
             Callee->hasAttr<NSReturnsRetainedAttr>() ||
             Callee->hasAttr<NSReturnsAutoreleasedAttr>()) {
           return callback(E, /*IsSafe=*/true,
-                          OriginDependsOnFullExpressionTemporary);
+                          OriginDependsOnFullExpressionTemporary,
+                          PtrIsLifetimeBoundToOrigin);
         }
       }
 
       if (isSafePtrType(call->getType()))
         return callback(E, /*IsSafe=*/true,
-                        OriginDependsOnFullExpressionTemporary);
+                        OriginDependsOnFullExpressionTemporary,
+                        PtrIsLifetimeBoundToOrigin);
 
       if (auto *memberCall = dyn_cast<CXXMemberCallExpr>(call)) {
         if (auto *decl = memberCall->getMethodDecl()) {
@@ -134,7 +259,8 @@ static bool tryToFindPtrOriginImpl(
             E = memberCall->getImplicitObjectArgument();
             if (StopAtFirstRefCountedObj) {
               return callback(E, /*IsSafe=*/true,
-                              OriginDependsOnFullExpressionTemporary);
+                              OriginDependsOnFullExpressionTemporary,
+                              PtrIsLifetimeBoundToOrigin);
             }
             continue;
           }
@@ -164,7 +290,8 @@ static bool tryToFindPtrOriginImpl(
         if (isCtorOfSafePtr(callee)) {
           if (StopAtFirstRefCountedObj)
             return callback(E, /*IsSafe=*/true,
-                            OriginDependsOnFullExpressionTemporary);
+                            OriginDependsOnFullExpressionTemporary,
+                            PtrIsLifetimeBoundToOrigin);
 
           E = call->getArg(0);
           continue;
@@ -177,11 +304,13 @@ static bool tryToFindPtrOriginImpl(
 
         if (isSafePtrType(callee->getReturnType()))
           return callback(E, /*IsSafe=*/true,
-                          OriginDependsOnFullExpressionTemporary);
+                          OriginDependsOnFullExpressionTemporary,
+                          PtrIsLifetimeBoundToOrigin);
 
         if (isSingleton(callee))
           return callback(E, /*IsSafe=*/true,
-                          OriginDependsOnFullExpressionTemporary);
+                          OriginDependsOnFullExpressionTemporary,
+                          PtrIsLifetimeBoundToOrigin);
 
         if (callee->isInStdNamespace() && safeGetName(callee) == "forward") {
           E = call->getArg(0);
@@ -199,12 +328,14 @@ static bool tryToFindPtrOriginImpl(
             Name == "NSStringFromClass" || Name == "NSClassFromString" ||
             Name == "NSStringFromProtocol" || Name == "NSProtocolFromString")
           return callback(E, /*IsSafe=*/true,
-                          OriginDependsOnFullExpressionTemporary);
+                          OriginDependsOnFullExpressionTemporary,
+                          PtrIsLifetimeBoundToOrigin);
       } else if (auto *CalleeE = call->getCallee()) {
         if (auto *E = dyn_cast<DeclRefExpr>(CalleeE->IgnoreParenCasts())) {
           if (isSingleton(E->getFoundDecl()))
             return callback(E, /*IsSafe=*/true,
-                            OriginDependsOnFullExpressionTemporary);
+                            OriginDependsOnFullExpressionTemporary,
+                            PtrIsLifetimeBoundToOrigin);
         }
 
         if (auto *MemberExpr = dyn_cast<CXXDependentScopeMemberExpr>(CalleeE)) 
{
@@ -213,7 +344,8 @@ static bool tryToFindPtrOriginImpl(
           bool IsGetter = MemberName == "get" || MemberName == "ptr";
           if (Base && isSafePtrType(Base->getType()) && IsGetter)
             return callback(E, /*IsSafe=*/true,
-                            OriginDependsOnFullExpressionTemporary);
+                            OriginDependsOnFullExpressionTemporary,
+                            PtrIsLifetimeBoundToOrigin);
         }
       }
 
@@ -229,38 +361,62 @@ static bool tryToFindPtrOriginImpl(
                 if (auto *CXX = dyn_cast<CXXRecordDecl>(RD->getDecl()))
                   if (isSafePtr(CXX))
                     return callback(E, /*IsSafe=*/true,
-                                    OriginDependsOnFullExpressionTemporary);
+                                    OriginDependsOnFullExpressionTemporary,
+                                    PtrIsLifetimeBoundToOrigin);
               }
             }
           }
         }
       }
+
+      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)
+          return tryToFindPtrOriginOfEach(
+              BoundArgs, StopAtFirstRefCountedObj, isSafePtr, isSafePtrType,
+              isSafeGlobalDecl, callback,
+              OriginDependsOnFullExpressionTemporary,
+              PtrIsLifetimeBoundToOrigin);
+      }
     }
     if (auto *ObjCMsgExpr = dyn_cast<ObjCMessageExpr>(E)) {
       if (auto *Method = ObjCMsgExpr->getMethodDecl()) {
         if (isSafePtrType(Method->getReturnType()))
           return callback(E, /*IsSafe=*/true,
-                          OriginDependsOnFullExpressionTemporary);
+                          OriginDependsOnFullExpressionTemporary,
+                          PtrIsLifetimeBoundToOrigin);
       }
       auto Selector = ObjCMsgExpr->getSelector();
       auto NameForFirstSlot = Selector.getNameForSlot(0);
       if ((NameForFirstSlot == "class" || NameForFirstSlot == "superclass") &&
           !Selector.getNumArgs())
         return callback(E, /*IsSafe=*/true,
-                        OriginDependsOnFullExpressionTemporary);
+                        OriginDependsOnFullExpressionTemporary,
+                        PtrIsLifetimeBoundToOrigin);
     }
     if (auto *ObjCProtocol = dyn_cast<ObjCProtocolExpr>(E))
       return callback(ObjCProtocol, /*IsSafe=*/true,
-                      OriginDependsOnFullExpressionTemporary);
+                      OriginDependsOnFullExpressionTemporary,
+                      PtrIsLifetimeBoundToOrigin);
     if (auto *ObjCDict = dyn_cast<ObjCDictionaryLiteral>(E))
       return callback(ObjCDict, /*IsSafe=*/true,
-                      OriginDependsOnFullExpressionTemporary);
+                      OriginDependsOnFullExpressionTemporary,
+                      PtrIsLifetimeBoundToOrigin);
     if (auto *ObjCArray = dyn_cast<ObjCArrayLiteral>(E))
       return callback(ObjCArray, /*IsSafe=*/true,
-                      OriginDependsOnFullExpressionTemporary);
+                      OriginDependsOnFullExpressionTemporary,
+                      PtrIsLifetimeBoundToOrigin);
     if (auto *ObjCStr = dyn_cast<ObjCStringLiteral>(E))
       return callback(ObjCStr, /*IsSafe=*/true,
-                      OriginDependsOnFullExpressionTemporary);
+                      OriginDependsOnFullExpressionTemporary,
+                      PtrIsLifetimeBoundToOrigin);
     if (auto *unaryOp = dyn_cast<UnaryOperator>(E)) {
       // FIXME: Currently accepts ANY unary operator. Is it OK?
       E = unaryOp->getSubExpr();
@@ -269,29 +425,33 @@ static bool tryToFindPtrOriginImpl(
     if (auto *BoxedExpr = dyn_cast<ObjCBoxedExpr>(E)) {
       if (StopAtFirstRefCountedObj)
         return callback(BoxedExpr, /*IsSafe=*/true,
-                        OriginDependsOnFullExpressionTemporary);
+                        OriginDependsOnFullExpressionTemporary,
+                        PtrIsLifetimeBoundToOrigin);
       E = BoxedExpr->getSubExpr();
       continue;
     }
     break;
   }
   // Some other expression.
-  return callback(E, /*IsSafe=*/false, OriginDependsOnFullExpressionTemporary);
+  return callback(E, /*IsSafe=*/false, OriginDependsOnFullExpressionTemporary,
+                  PtrIsLifetimeBoundToOrigin);
 }
 
 bool tryToFindPtrOrigin(
-    const Expr *E, bool StopAtFirstRefCountedObj,
+    const Expr *E, bool StopAtFirstRefCountedObj, bool FollowLifetimeBound,
     std::function<bool(const clang::CXXRecordDecl *)> isSafePtr,
     std::function<bool(const clang::QualType)> isSafePtrType,
     std::function<bool(const clang::Decl *)> isSafeGlobalDecl,
     std::function<bool(const clang::Expr *, bool /*IsSafe*/,
-                       bool /*OriginDependsOnFullExpressionTemporary*/)>
+                       bool /*OriginDependsOnFullExpressionTemporary*/,
+                       bool /*PtrIsLifetimeBoundToOrigin*/)>
         callback) {
   return tryToFindPtrOriginImpl(
-      E, StopAtFirstRefCountedObj, std::move(isSafePtr),
+      E, StopAtFirstRefCountedObj, FollowLifetimeBound, std::move(isSafePtr),
       std::move(isSafePtrType), std::move(isSafeGlobalDecl),
       std::move(callback),
-      /*OriginDependsOnFullExpressionTemporary=*/false);
+      /*OriginDependsOnFullExpressionTemporary=*/false,
+      /*PtrIsLifetimeBoundToOrigin=*/false);
 }
 
 bool isASafeCallArg(const Expr *E) {

diff  --git a/clang/lib/StaticAnalyzer/Checkers/WebKit/ASTUtils.h 
b/clang/lib/StaticAnalyzer/Checkers/WebKit/ASTUtils.h
index 5507dd239affbb..67369521348215 100644
--- a/clang/lib/StaticAnalyzer/Checkers/WebKit/ASTUtils.h
+++ b/clang/lib/StaticAnalyzer/Checkers/WebKit/ASTUtils.h
@@ -50,18 +50,24 @@ class Expr;
 /// sub-expression and true.
 ///
 /// Calls \p callback for each origin the traversal reaches, passing the
-/// subexpression, whether the traversal recognized it as a safe origin, and
+/// subexpression, whether the traversal recognized it as a safe origin,
 /// whether the path to it passed through a temporary that dies at the end of
-/// the full-expression; in that case the origin's lifetime guarantee cannot
-/// be assumed to extend past the full-expression. Returns false if any of
-/// calls to callbacks returned false. Otherwise true.
+/// the full-expression (in that case the origin's lifetime guarantee cannot
+/// be assumed to extend past the full-expression), and whether the path to it
+/// followed at least one [[clang::lifetimebound]] edge. Returns false if any
+/// of calls to callbacks returned false. Otherwise true.
+///
+/// If \p FollowLifetimeBound is true, f(x [[clang::lifetimebound]])
+/// traverses into x.
 bool tryToFindPtrOrigin(
     const clang::Expr *E, bool StopAtFirstRefCountedObj,
+    bool FollowLifetimeBound,
     std::function<bool(const clang::CXXRecordDecl *)> isSafePtr,
     std::function<bool(const clang::QualType)> isSafePtrType,
     std::function<bool(const clang::Decl *)> isSafeGlobalDecl,
     std::function<bool(const clang::Expr *, bool /*IsSafe*/,
-                       bool /*OriginDependsOnFullExpressionTemporary*/)>
+                       bool /*OriginDependsOnFullExpressionTemporary*/,
+                       bool /*PtrIsLifetimeBoundToOrigin*/)>
         callback);
 
 /// For \p E referring to a ref-countable/-counted pointer/reference we return

diff  --git a/clang/lib/StaticAnalyzer/Checkers/WebKit/PtrTypesSemantics.cpp 
b/clang/lib/StaticAnalyzer/Checkers/WebKit/PtrTypesSemantics.cpp
index 50bb4c83833a6f..b38bbba2c173f3 100644
--- a/clang/lib/StaticAnalyzer/Checkers/WebKit/PtrTypesSemantics.cpp
+++ b/clang/lib/StaticAnalyzer/Checkers/WebKit/PtrTypesSemantics.cpp
@@ -12,8 +12,10 @@
 #include "clang/AST/CXXInheritance.h"
 #include "clang/AST/Decl.h"
 #include "clang/AST/DeclCXX.h"
+#include "clang/AST/DeclTemplate.h"
 #include "clang/AST/ExprCXX.h"
 #include "clang/AST/StmtVisitor.h"
+#include "clang/Analysis/Analyses/LifetimeSafety/LifetimeAnnotations.h"
 #include "clang/Analysis/DomainSpecific/CocoaConventions.h"
 #include <optional>
 
@@ -131,6 +133,64 @@ std::optional<bool> isCheckedPtrCapable(const 
clang::CXXRecordDecl *R) {
                               "decrementCheckedPtrCount");
 }
 
+std::optional<bool> isBorrowable(const clang::CXXRecordDecl *R) {
+  assert(R);
+  return hasPublicMethodInHierarchy(R, "crashIfBorrowed");
+}
+
+bool isBorrow(const clang::CXXRecordDecl *R) {
+  if (!R)
+    return false;
+  return isBorrow(safeGetName(R));
+}
+
+bool isBorrowType(const clang::QualType T) {
+  return isBorrow(T->getAsCXXRecordDecl());
+}
+
+QualType pointeeType(QualType T) {
+  while (!T.isNull()) {
+    QualType Pointee = T->getPointeeType();
+    if (Pointee.isNull())
+      break;
+    T = Pointee;
+  }
+  return T;
+}
+
+QualType borrowedType(QualType T) {
+  const auto *Specialization =
+      dyn_cast_or_null<ClassTemplateSpecializationDecl>(
+          T->getAsCXXRecordDecl());
+  if (!Specialization)
+    return QualType();
+  const auto &Args = Specialization->getTemplateArgs();
+  if (!Args.size() || Args[0].getKind() != TemplateArgument::Type)
+    return QualType();
+  return Args[0].getAsType();
+}
+
+static bool hasLifetimeBoundCtor(const clang::CXXRecordDecl *R) {
+  if (!R || !R->hasDefinition())
+    return false;
+  for (const CXXConstructorDecl *Ctor : R->ctors()) {
+    for (const ParmVarDecl *Param : Ctor->parameters()) {
+      if (Param->hasAttr<LifetimeBoundAttr>() ||
+          Param->hasAttr<LifetimeCaptureByAttr>())
+        return true;
+    }
+  }
+  return false;
+}
+
+bool isView(const clang::QualType T) {
+  if (T->isReferenceType())
+    return true;
+  if (lifetimes::isPointerLikeType(T))
+    return true;
+  return hasLifetimeBoundCtor(T->getAsCXXRecordDecl());
+}
+
 bool isRefType(const std::string &Name) {
   return Name == "Ref" || Name == "RefAllowingPartiallyDestroyed" ||
          Name == "RefPtr" || Name == "RefPtrAllowingPartiallyDestroyed";
@@ -149,6 +209,8 @@ bool isUniquePtr(const std::string &Name) {
   return Name == "unique_ptr" || Name == "UniqueRef" || Name == 
"LazyUniqueRef";
 }
 
+bool isBorrow(const std::string &Name) { return Name == "Borrow"; }
+
 bool isOwnerPtr(const std::string &Name) {
   return isRefType(Name) || isCheckedPtr(Name) || isRetainPtrOrOSPtr(Name) ||
          isUniquePtr(Name);

diff  --git a/clang/lib/StaticAnalyzer/Checkers/WebKit/PtrTypesSemantics.h 
b/clang/lib/StaticAnalyzer/Checkers/WebKit/PtrTypesSemantics.h
index 3a58b68f50a9a4..9e9bb995f7ca2e 100644
--- a/clang/lib/StaticAnalyzer/Checkers/WebKit/PtrTypesSemantics.h
+++ b/clang/lib/StaticAnalyzer/Checkers/WebKit/PtrTypesSemantics.h
@@ -52,6 +52,27 @@ std::optional<bool> isRefCountable(const 
clang::CXXRecordDecl *Class);
 /// std::nullopt if inconclusive.
 std::optional<bool> isCheckedPtrCapable(const clang::CXXRecordDecl *Class);
 
+/// \returns true if \p Class implements the CanBorrow protocol, meaning a
+/// Borrow<T> can be taken on it, false if not, std::nullopt if inconclusive.
+std::optional<bool> isBorrowable(const clang::CXXRecordDecl *Class);
+
+/// \returns true if \p Class is a Borrow<T>, false if not.
+bool isBorrow(const clang::CXXRecordDecl *Class);
+
+/// \returns true if \p T is a Borrow<T>.
+bool isBorrowType(const clang::QualType T);
+
+/// \returns the innermost type reached by stripping every pointer/reference
+/// layer from \p T; \p T itself if it has none; a null type if \p T is null.
+clang::QualType pointeeType(clang::QualType T);
+
+/// \returns the type a Borrow<T> specialization \p T borrows, or a null type
+/// if \p T is not a template specialization whose first argument is a type.
+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 is ref-counted, false if not.
 bool isRefCounted(const clang::CXXRecordDecl *Class);
 
@@ -142,6 +163,9 @@ bool isRefType(const std::string &Name);
 /// \returns true if \p Name is CheckedRef or CheckedPtr, false if not.
 bool isCheckedPtr(const std::string &Name);
 
+/// \returns true if \p Name is Borrow, false if not.
+bool isBorrow(const std::string &Name);
+
 /// \returns true if \p Name is RetainPtr or its variant, false if not.
 bool isRetainPtrOrOSPtr(const std::string &Name);
 

diff  --git 
a/clang/lib/StaticAnalyzer/Checkers/WebKit/RawPtrRefCallArgsChecker.cpp 
b/clang/lib/StaticAnalyzer/Checkers/WebKit/RawPtrRefCallArgsChecker.cpp
index 3c5a2f708bff75..240694d34df540 100644
--- a/clang/lib/StaticAnalyzer/Checkers/WebKit/RawPtrRefCallArgsChecker.cpp
+++ b/clang/lib/StaticAnalyzer/Checkers/WebKit/RawPtrRefCallArgsChecker.cpp
@@ -245,6 +245,7 @@ class RawPtrRefCallArgsChecker
   bool isPtrOriginSafe(const Expr *Arg) const {
     return tryToFindPtrOrigin(
         Arg, /*StopAtFirstRefCountedObj=*/true,
+        Model->checksForInteriorDestruction(),
         [&](const clang::CXXRecordDecl *Record) {
           return Model->isSafePtr(Record);
         },
@@ -255,7 +256,8 @@ class RawPtrRefCallArgsChecker
         // A temporary on the path to an argument's origin is safe: the full
         // expression does not end until the call returns.
         [&](const clang::Expr *ArgOrigin, bool IsSafe,
-            bool /*OriginDependsOnFullExpressionTemporary*/) {
+            bool /*OriginDependsOnFullExpressionTemporary*/,
+            bool PtrIsLifetimeBoundToOrigin) {
           if (IsSafe)
             return true;
           if (isNullPtr(ArgOrigin))
@@ -277,7 +279,7 @@ class RawPtrRefCallArgsChecker
             if (isPtrOriginSafe(MCE->getImplicitObjectArgument()))
               return true;
           }
-          if (Model->isSafeExpr(ArgOrigin))
+          if (Model->isSafeExpr(ArgOrigin, PtrIsLifetimeBoundToOrigin))
             return true;
           return false;
         });

diff  --git 
a/clang/lib/StaticAnalyzer/Checkers/WebKit/RawPtrRefLocalVarsChecker.cpp 
b/clang/lib/StaticAnalyzer/Checkers/WebKit/RawPtrRefLocalVarsChecker.cpp
index d648c7f4f45d68..dfb2349b390f0b 100644
--- a/clang/lib/StaticAnalyzer/Checkers/WebKit/RawPtrRefLocalVarsChecker.cpp
+++ b/clang/lib/StaticAnalyzer/Checkers/WebKit/RawPtrRefLocalVarsChecker.cpp
@@ -214,6 +214,25 @@ bool isGuardedScopeEmbeddedInGuardianScope(const VarDecl 
*Guarded,
   return false;
 }
 
+static const VarDecl *findAssignedVar(const Expr *DestExpr) {
+  while (DestExpr) {
+    DestExpr = DestExpr->IgnoreParenCasts();
+    if (auto *DRE = dyn_cast<DeclRefExpr>(DestExpr))
+      return dyn_cast_or_null<VarDecl>(DRE->getDecl());
+    if (auto *UO = dyn_cast<UnaryOperator>(DestExpr);
+        UO && UO->getOpcode() == UO_Deref) {
+      DestExpr = UO->getSubExpr();
+      continue;
+    }
+    if (auto *ASE = dyn_cast<ArraySubscriptExpr>(DestExpr)) {
+      DestExpr = ASE->getBase();
+      continue;
+    }
+    return nullptr;
+  }
+  return nullptr;
+}
+
 class RawPtrRefLocalVarsChecker
     : public Checker<check::ASTDecl<TranslationUnitDecl>> {
   BugType Bug;
@@ -275,7 +294,11 @@ class RawPtrRefLocalVarsChecker
 
       bool VisitBinaryOperator(BinaryOperator *BO) override {
         if (BO->isAssignmentOp()) {
-          if (auto *VarRef = dyn_cast<DeclRefExpr>(BO->getLHS())) {
+          if (Checker->Model->recognizesIndirectStores()) {
+            if (auto *V = findAssignedVar(BO->getLHS()))
+              Checker->visitVarDecl(V, BO->getLHS()->getType(), BO->getRHS(),
+                                    DeclWithIssue);
+          } else if (auto *VarRef = dyn_cast<DeclRefExpr>(BO->getLHS())) {
             if (auto *V = dyn_cast<VarDecl>(VarRef->getDecl()))
               Checker->visitVarDecl(V, V->getType(), BO->getRHS(),
                                     DeclWithIssue);
@@ -341,29 +364,48 @@ class RawPtrRefLocalVarsChecker
       return;
 
     if (auto *DD = dyn_cast<DecompositionDecl>(V)) {
+      const auto *InitList =
+          Value ? dyn_cast<InitListExpr>(Value->IgnoreParenCasts()) : nullptr;
+      if (InitList && InitList->getNumInits() != DD->bindings().size())
+        InitList = nullptr;
+
+      unsigned BindingIndex = 0;
       for (auto *BD : DD->bindings()) {
+        const unsigned Index = BindingIndex++;
         auto *Binding = BD->getBinding();
         if (!Binding)
           continue;
         std::optional<bool> IsUncountedPtr = isUnsafePtr(Binding->getType());
         if (!IsUncountedPtr || !*IsUncountedPtr)
           continue;
-        reportBug(V, V->getType(), nullptr, BD, DeclWithIssue);
+
+        const Expr *Origin = nullptr;
+        if (Model->checksForInteriorDestruction()) {
+          const Expr *Source = InitList ? InitList->getInit(Index) : Value;
+          if (isPtrOriginSafe(V, Source, DeclWithIssue, Origin))
+            continue;
+        }
+        reportBug(V, V->getType(), nullptr, BD, DeclWithIssue, Origin);
       }
     }
 
     std::optional<bool> IsUncountedPtr = isUnsafePtr(SinkType);
     if (IsUncountedPtr && *IsUncountedPtr) {
-      if (Value && isPtrOriginSafe(V, Value, DeclWithIssue))
+      const Expr *Origin = nullptr;
+      if (Value) {
+        if (isPtrOriginSafe(V, Value, DeclWithIssue, Origin))
+          return;
+      } else if (Model->checksForInteriorDestruction())
         return;
-      reportBug(V, SinkType, Value, nullptr, DeclWithIssue);
+      reportBug(V, SinkType, Value, nullptr, DeclWithIssue, Origin);
     }
   }
 
   bool isPtrOriginSafe(const VarDecl *V, const Expr *Value,
-                       const Decl *DeclWithIssue) const {
+                       const Decl *DeclWithIssue, const Expr *&Origin) const {
     return tryToFindPtrOrigin(
         Value, /*StopAtFirstRefCountedObj=*/false,
+        Model->checksForInteriorDestruction(),
         [&](const clang::CXXRecordDecl *Record) {
           return Model->isSafePtr(Record);
         },
@@ -372,12 +414,18 @@ class RawPtrRefLocalVarsChecker
           return Model->isSafeDecl(D, BR->getSourceManager());
         },
         [&](const clang::Expr *InitArgOrigin, bool IsSafe,
-            bool OriginDependsOnFullExpressionTemporary) {
+            bool OriginDependsOnFullExpressionTemporary,
+            bool PtrIsLifetimeBoundToOrigin) {
           if (!InitArgOrigin)
             return true;
 
-          if (IsSafe)
-            return !OriginDependsOnFullExpressionTemporary;
+          if (IsSafe) {
+            if (!OriginDependsOnFullExpressionTemporary)
+              return true;
+            if (!Origin)
+              Origin = InitArgOrigin;
+            return false;
+          }
 
           if (isa<CXXThisExpr>(InitArgOrigin))
             return true;
@@ -394,12 +442,15 @@ class RawPtrRefLocalVarsChecker
           if (EFA.isACallToEnsureFn(InitArgOrigin))
             return true;
 
-          if (Model->isSafeExpr(InitArgOrigin))
+          if (Model->isSafeExpr(InitArgOrigin, PtrIsLifetimeBoundToOrigin))
             return true;
 
-          if (hasGuardian(V, InitArgOrigin, DeclWithIssue))
+          if (!Model->checksForInteriorDestruction() &&
+              hasGuardian(V, InitArgOrigin, DeclWithIssue))
             return true;
 
+          if (!Origin)
+            Origin = InitArgOrigin;
           return false;
         });
   }
@@ -447,7 +498,8 @@ class RawPtrRefLocalVarsChecker
   }
 
   void reportBug(const VarDecl *V, QualType SinkType, const Expr *Value,
-                 const Decl *BindingDecl, const Decl *DeclWithIssue) const {
+                 const Decl *BindingDecl, const Decl *DeclWithIssue,
+                 const Expr *Origin) const {
     assert(V);
     SmallString<100> Buf;
     llvm::raw_svector_ostream Os(Buf);
@@ -456,7 +508,7 @@ class RawPtrRefLocalVarsChecker
       Os << "Parameter ";
       printQuotedQualifiedName(Os, V);
       Os << " is a ";
-      printPointerTypeAndType(Os, SinkType);
+      Model->describeHazard(Os, Origin, SinkType);
 
       SourceLocation ExprLoc = (Value) ? Value->getExprLoc() : 
V->getLocation();
       PathDiagnosticLocation BSLoc(ExprLoc, BR->getSourceManager());
@@ -479,7 +531,7 @@ class RawPtrRefLocalVarsChecker
       else
         printQuotedQualifiedName(Os, V);
       Os << " is a ";
-      printPointerTypeAndType(Os, SinkType);
+      Model->describeHazard(Os, Origin, SinkType);
 
       PathDiagnosticLocation BSLoc(V->getLocation(), BR->getSourceManager());
       auto Report = std::make_unique<BasicBugReport>(Bug, Os.str(), BSLoc);
@@ -488,28 +540,6 @@ class RawPtrRefLocalVarsChecker
       BR->emitReport(std::move(Report));
     }
   }
-
-  void printPointerTypeAndType(llvm::raw_svector_ostream &Os,
-                               QualType QT) const {
-    auto *VarType = QT.getTypePtr();
-    auto *RTC = Model->retainTypeChecker();
-    if (RTC && isa<TypedefType>(VarType)) {
-      Os << Model->typeName() << " ";
-      if (auto *Decl = RTC->getCanonicalDecl(QT)) {
-        printQuotedQualifiedName(Os, Decl);
-      } else {
-        auto Typedef = VarType->getAs<TypedefType>();
-        assert(Typedef);
-        printQuotedQualifiedName(Os, Typedef->getDecl());
-      }
-    } else {
-      auto *DesugaredType = VarType->getUnqualifiedDesugaredType();
-      bool IsPtr = isa<PointerType, ObjCObjectPointerType>(DesugaredType);
-      Os << "raw " << (IsPtr ? "pointer" : "reference") << " to ";
-      Os << Model->typeName() << " ";
-      printTypeName(Os, QT);
-    }
-  }
 };
 
 class UncountedLocalVarsChecker final : public RawPtrRefLocalVarsChecker {
@@ -536,6 +566,14 @@ class UnretainedLocalVarsChecker final : public 
RawPtrRefLocalVarsChecker {
                                   makeRetainPtrSafetyModel()) {}
 };
 
+class UnborrowedLocalVarsChecker final : public RawPtrRefLocalVarsChecker {
+public:
+  UnborrowedLocalVarsChecker()
+      : RawPtrRefLocalVarsChecker("Loan on a CanBorrow object not guarded by "
+                                  "a Borrow",
+                                  makeBorrowSafetyModel()) {}
+};
+
 } // namespace
 
 void ento::registerUncountedLocalVarsChecker(CheckerManager &Mgr) {
@@ -561,3 +599,11 @@ void 
ento::registerUnretainedLocalVarsChecker(CheckerManager &Mgr) {
 bool ento::shouldRegisterUnretainedLocalVarsChecker(const CheckerManager &) {
   return true;
 }
+
+void ento::registerUnborrowedLocalVarsChecker(CheckerManager &Mgr) {
+  Mgr.registerChecker<UnborrowedLocalVarsChecker>();
+}
+
+bool ento::shouldRegisterUnborrowedLocalVarsChecker(const CheckerManager &) {
+  return true;
+}

diff  --git a/clang/lib/StaticAnalyzer/Checkers/WebKit/RawPtrRefSafetyModel.cpp 
b/clang/lib/StaticAnalyzer/Checkers/WebKit/RawPtrRefSafetyModel.cpp
index 0104cb295d7ef6..d0e8c1bee899e8 100644
--- a/clang/lib/StaticAnalyzer/Checkers/WebKit/RawPtrRefSafetyModel.cpp
+++ b/clang/lib/StaticAnalyzer/Checkers/WebKit/RawPtrRefSafetyModel.cpp
@@ -8,7 +8,9 @@
 
 #include "RawPtrRefSafetyModel.h"
 #include "ASTUtils.h"
+#include "DiagOutputUtils.h"
 #include "clang/AST/Decl.h"
+#include "clang/AST/Expr.h"
 #include "clang/AST/ExprObjC.h"
 #include "clang/AST/Type.h"
 #include "clang/Analysis/DomainSpecific/CocoaConventions.h"
@@ -55,7 +57,7 @@ class CheckedPtrSafetyModel : public PtrRefSafetyModel {
   bool isPtrType(const std::string &Name) const override {
     return isCheckedPtr(Name);
   }
-  bool isSafeExpr(const Expr *E) const override {
+  bool isSafeExpr(const Expr *E, bool) const override {
     return isExprToGetCheckedPtrCapableMember(E);
   }
   const char *typeName() const override { return "CheckedPtr-capable type"; }
@@ -80,7 +82,7 @@ class RetainPtrSafetyModel : public PtrRefSafetyModel {
   bool isPtrType(const std::string &Name) const override {
     return isRetainPtrOrOSPtr(Name);
   }
-  bool isSafeExpr(const Expr *E) const override {
+  bool isSafeExpr(const Expr *E, bool) const override {
     return ento::cocoa::isCocoaObjectRef(E->getType()) &&
            isa<ObjCMessageExpr>(E);
   }
@@ -88,10 +90,86 @@ class RetainPtrSafetyModel : public PtrRefSafetyModel {
     // Treat NS/CF globals in system header as immortal.
     return SM.isInSystemHeader(D->getLocation());
   }
+  void describeHazard(llvm::raw_ostream &Os, const Expr *Origin,
+                      QualType SinkType) const override {
+    auto *VarType = SinkType.getTypePtr();
+    if (isa<TypedefType>(VarType)) {
+      Os << typeName() << " ";
+      if (auto *Decl = RTC.getCanonicalDecl(SinkType)) {
+        printQuotedQualifiedName(Os, Decl);
+      } else {
+        const auto *Typedef = VarType->getAs<TypedefType>();
+        assert(Typedef);
+        printQuotedQualifiedName(Os, Typedef->getDecl());
+      }
+      return;
+    }
+    PtrRefSafetyModel::describeHazard(Os, Origin, SinkType);
+  }
   const char *typeName() const override { return "RetainPtr-capable type"; }
   RetainTypeChecker *retainTypeChecker() const override { return &RTC; }
 };
 
+class BorrowSafetyModel : public PtrRefSafetyModel {
+public:
+  std::optional<bool> isUnsafeType(QualType QT) const override {
+    return isView(QT);
+  }
+  std::optional<bool> isUnsafePtr(QualType QT, bool) const override {
+    return isView(QT);
+  }
+  bool isSafePtr(const CXXRecordDecl *Record) const override {
+    return isBorrow(Record);
+  }
+  bool isSafePtrType(QualType T) const override { return isBorrowType(T); }
+  bool isPtrType(const std::string &Name) const override {
+    return isBorrow(Name);
+  }
+
+  bool isSafeExpr(const Expr *Origin,
+                  bool PtrIsLifetimeBoundToOrigin) const override {
+    if (!PtrIsLifetimeBoundToOrigin)
+      return true;
+
+    QualType OriginType = pointeeType(Origin->getType());
+
+    if (OriginType.isNull())
+      return true;
+
+    if (isBorrowType(OriginType))
+      return true;
+
+    auto *Record = OriginType->getAsCXXRecordDecl();
+    if (!Record)
+      return true;
+
+    auto Borrowable = isBorrowable(Record);
+    return !Borrowable || !*Borrowable;
+  }
+
+  bool checksForInteriorDestruction() const override { return true; }
+  bool recognizesIndirectStores() const override { return true; }
+  const char *typeName() const override { return "CanBorrow type"; }
+
+  void describeHazard(llvm::raw_ostream &Os, const Expr *Origin,
+                      QualType) const override {
+    Os << "loan on ";
+    QualType OriginType = Origin ? pointeeType(Origin->getType()) : QualType();
+
+    // Name the borrowed type, not the Borrow<T> guard, when the loan was
+    // taken from a Borrow<T> temporary.
+    if (!OriginType.isNull() && isBorrowType(OriginType))
+      OriginType = borrowedType(OriginType);
+
+    if (!OriginType.isNull() && OriginType->getAsRecordDecl()) {
+      Os << "CanBorrow type ";
+      printTypeName(Os, OriginType);
+    } else
+      Os << "a CanBorrow object";
+    Os << " that is not guarded by a Borrow";
+  }
+};
+
 } // namespace
 
 std::optional<bool> clang::isUnsafePtrForStorage(const PtrRefSafetyModel 
&Model,
@@ -115,3 +193,7 @@ std::unique_ptr<PtrRefSafetyModel> 
clang::makeCheckedPtrSafetyModel() {
 std::unique_ptr<PtrRefSafetyModel> clang::makeRetainPtrSafetyModel() {
   return std::make_unique<RetainPtrSafetyModel>();
 }
+
+std::unique_ptr<PtrRefSafetyModel> clang::makeBorrowSafetyModel() {
+  return std::make_unique<BorrowSafetyModel>();
+}

diff  --git a/clang/lib/StaticAnalyzer/Checkers/WebKit/RawPtrRefSafetyModel.h 
b/clang/lib/StaticAnalyzer/Checkers/WebKit/RawPtrRefSafetyModel.h
index fdd10f5562b518..87dc93a9f050ab 100644
--- a/clang/lib/StaticAnalyzer/Checkers/WebKit/RawPtrRefSafetyModel.h
+++ b/clang/lib/StaticAnalyzer/Checkers/WebKit/RawPtrRefSafetyModel.h
@@ -9,7 +9,10 @@
 #ifndef LLVM_CLANG_ANALYZER_WEBKIT_RAWPTRREFSAFETYMODEL_H
 #define LLVM_CLANG_ANALYZER_WEBKIT_RAWPTRREFSAFETYMODEL_H
 
+#include "DiagOutputUtils.h"
 #include "PtrTypesSemantics.h"
+#include "clang/AST/Type.h"
+#include "llvm/Support/raw_ostream.h"
 #include <memory>
 #include <optional>
 #include <string>
@@ -37,7 +40,7 @@ class PtrRefSafetyModel {
   /// managed) type, false if not, std::nullopt if inconclusive.
   virtual std::optional<bool> isUnsafeType(QualType QT) const = 0;
 
-  /// \returns whether \p QT is a raw pointer or reference to an unsafe type,
+  /// \returns whether \p QT is a pointer/reference/view to an analyzed type,
   /// false if not, std::nullopt if inconclusive. \p IgnoreARC requests that
   /// Objective-C ARC be ignored when deciding retainability.
   virtual std::optional<bool> isUnsafePtr(QualType QT,
@@ -54,7 +57,23 @@ class PtrRefSafetyModel {
   virtual bool isPtrType(const std::string &Name) const = 0;
 
   /// \returns whether \p E is known to produce a safe value for this policy.
-  virtual bool isSafeExpr(const Expr *) const { return false; }
+  /// \p PtrIsLifetimeBoundToOrigin is whether the traversal that reached \p E
+  /// followed at least one [[clang::lifetimebound]] edge.
+  virtual bool isSafeExpr(const Expr *, bool PtrIsLifetimeBoundToOrigin) const 
{
+    return false;
+  }
+
+  /// \returns whether this policy checks for destruction of an object's
+  /// interior while the object itself stays alive (borrow checking), rather
+  /// than for deallocation of the object (the smart pointer policies).
+  virtual bool checksForInteriorDestruction() const { return false; }
+
+  /// \returns whether this policy checks assignment through indirection, such
+  /// as *out = _ or arr[0] = _. (Direct assignment to a named variable is
+  /// always checked.)
+  ///
+  /// FIXME: Make this flag true in all analyses and then remove it.
+  virtual bool recognizesIndirectStores() const { return false; }
 
   /// \returns whether \p D refers to a declaration that is safe by 
construction
   /// for this policy (e.g. immortal system-header globals).
@@ -66,6 +85,20 @@ class PtrRefSafetyModel {
   /// diagnostics (e.g. "RefPtr-capable type").
   virtual const char *typeName() const = 0;
 
+  /// Prints a phrase describing why the reported value is unsafe, completing a
+  /// sentence of the form "Local variable 'x' is a ". \p Origin is the
+  /// expression the value was traced back to, or null when the trace found
+  /// none. \p SinkType is the type of the reported location.
+  virtual void describeHazard(llvm::raw_ostream &Os, const Expr *,
+                              QualType SinkType) const {
+    auto *VarType = SinkType.getTypePtr();
+    auto *DesugaredType = VarType->getUnqualifiedDesugaredType();
+    bool IsPtr = isa<PointerType, ObjCObjectPointerType>(DesugaredType);
+    Os << "raw " << (IsPtr ? "pointer" : "reference") << " to ";
+    Os << typeName() << " ";
+    printTypeName(Os, SinkType);
+  }
+
   /// \returns the RetainTypeChecker backing this policy, or nullptr if the
   /// policy does not track retain/OS types.
   virtual RetainTypeChecker *retainTypeChecker() const { return nullptr; }
@@ -87,6 +120,10 @@ std::unique_ptr<PtrRefSafetyModel> 
makeCheckedPtrSafetyModel();
 /// \returns a policy that treats RetainPtr / OSPtr as safe.
 std::unique_ptr<PtrRefSafetyModel> makeRetainPtrSafetyModel();
 
+/// \returns a policy that treats a loan on a CanBorrow object's interior as
+/// safe only when it is guarded by a Borrow<T>.
+std::unique_ptr<PtrRefSafetyModel> makeBorrowSafetyModel();
+
 } // namespace clang
 
 #endif

diff  --git a/clang/test/Analysis/Checkers/WebKit/mock-canborrow.h 
b/clang/test/Analysis/Checkers/WebKit/mock-canborrow.h
new file mode 100644
index 00000000000000..84737b30788a81
--- /dev/null
+++ b/clang/test/Analysis/Checkers/WebKit/mock-canborrow.h
@@ -0,0 +1,220 @@
+#ifndef mock_canborrow_h
+#define mock_canborrow_h
+
+#define LIFETIME_BOUND [[clang::lifetimebound]]
+
+class CanBorrow {
+public:
+  ~CanBorrow() { crashIfBorrowed(); }
+
+  void crashIfBorrowed() const {}
+
+  bool setIsBorrowed(bool isBorrowed) const {
+    bool previous = m_isBorrowed;
+    m_isBorrowed = isBorrowed;
+    return previous;
+  }
+
+private:
+  mutable bool m_isBorrowed { false };
+};
+
+template <typename T> class Borrow {
+public:
+  Borrow(T &ref LIFETIME_BOUND)
+      : m_ref(ref), m_previous(ref.setIsBorrowed(true)) {}
+  ~Borrow() { m_ref.setIsBorrowed(m_previous); }
+
+  Borrow(const Borrow &) = delete;
+  Borrow &operator=(const Borrow &) = delete;
+
+  operator T &() const LIFETIME_BOUND { return m_ref; }
+  T &get() const LIFETIME_BOUND { return m_ref; }
+  T *operator->() const LIFETIME_BOUND { return &m_ref; }
+
+private:
+  T &m_ref;
+  bool m_previous;
+};
+
+template <typename T> Borrow(T &) -> Borrow<T>;
+
+template <typename T> Borrow<T> borrow(T &ref LIFETIME_BOUND) {
+  return Borrow<T>(ref);
+}
+
+template <typename T> class VectorBufferBase {
+public:
+  void crashIfBorrowed() const {}
+
+  bool setIsBorrowed(bool isBorrowed) const {
+    bool previous = m_isBorrowed;
+    m_isBorrowed = isBorrowed;
+    return previous;
+  }
+
+protected:
+  T *m_buffer { nullptr };
+  mutable bool m_isBorrowed { false };
+};
+
+template <typename T> class VectorBuffer : private VectorBufferBase<T> {
+  typedef VectorBufferBase<T> Base;
+
+public:
+  using Base::crashIfBorrowed;
+  using Base::setIsBorrowed;
+
+protected:
+  using Base::m_buffer;
+};
+
+template <typename T> class Vector : private VectorBuffer<T> {
+  typedef VectorBuffer<T> Buffer;
+
+public:
+  using Buffer::setIsBorrowed;
+
+  T &operator[](unsigned i) LIFETIME_BOUND { return Buffer::m_buffer[i]; }
+  const T &operator[](unsigned i) const LIFETIME_BOUND {
+    return Buffer::m_buffer[i];
+  }
+  T *data() LIFETIME_BOUND { return Buffer::m_buffer; }
+  const T *data() const LIFETIME_BOUND { return Buffer::m_buffer; }
+  T *begin() LIFETIME_BOUND { return Buffer::m_buffer; }
+  T *end() LIFETIME_BOUND { return Buffer::m_buffer + m_size; }
+  const T *begin() const LIFETIME_BOUND { return Buffer::m_buffer; }
+  const T *end() const LIFETIME_BOUND { return Buffer::m_buffer + m_size; }
+  unsigned size() const { return m_size; }
+
+  void append(const T &);
+
+private:
+  unsigned m_size { 0 };
+};
+
+template <typename T> class SimpleContainer : public CanBorrow {
+public:
+  T &operator[](unsigned i) LIFETIME_BOUND { return m_buffer[i]; }
+  void append(const T &);
+
+private:
+  T *m_buffer { nullptr };
+};
+
+class StringView {
+public:
+  StringView() = default;
+  StringView(const char *data LIFETIME_BOUND) : m_data(data) {}
+
+private:
+  const char *m_data { nullptr };
+};
+
+class [[gsl::Pointer]] CharSpan {
+public:
+  CharSpan() = default;
+  CharSpan(char *data) : m_data(data) {}
+
+private:
+  char *m_data { nullptr };
+};
+
+class NotBorrowable {
+public:
+  char &at(unsigned i) LIFETIME_BOUND { return m_buffer[i]; }
+  void mutate();
+
+private:
+  char *m_buffer { nullptr };
+};
+
+template <typename T> class Owner {
+public:
+  T *get() const LIFETIME_BOUND { return m_ptr; }
+  T &operator*() const LIFETIME_BOUND { return *m_ptr; }
+  T *operator->() const LIFETIME_BOUND { return m_ptr; }
+
+private:
+  T *m_ptr { nullptr };
+};
+
+CharSpan makeSpan(Vector<char> &vec LIFETIME_BOUND);
+StringView makeView(const char *data LIFETIME_BOUND);
+
+CharSpan makeSpanUnannotated(Vector<char> &vec);
+
+Vector<char> &forwardRef(Vector<char> &vec LIFETIME_BOUND);
+Vector<char> *forwardPtr(Vector<char> &vec LIFETIME_BOUND);
+
+class Node : public CanBorrow {
+public:
+  Node &firstChild() LIFETIME_BOUND { return m_children[0]; }
+  void appendChild();
+
+private:
+  Vector<Node> m_children;
+};
+
+const char *pick(const char *a LIFETIME_BOUND, const char *b LIFETIME_BOUND);
+
+class TwoStringViews {
+public:
+  TwoStringViews(const char *a LIFETIME_BOUND, const char *b LIFETIME_BOUND);
+};
+
+template <typename T> class Registry;
+
+template <typename T> class Cursor {
+public:
+  Cursor(Registry<T> &registry LIFETIME_BOUND, unsigned index)
+      : m_registry(&registry), m_index(index) {}
+
+  T &value() const LIFETIME_BOUND { return m_registry->at(m_index); }
+  void remove() const { m_registry->removeAt(m_index); }
+
+private:
+  Registry<T> *m_registry;
+  unsigned m_index;
+};
+
+template <typename T> class RegistryIterator {
+public:
+  RegistryIterator(Registry<T> &registry LIFETIME_BOUND, unsigned index)
+      : m_registry(&registry), m_index(index) {}
+
+  Cursor<T> operator*() const LIFETIME_BOUND {
+    return Cursor<T>(*m_registry, m_index);
+  }
+  RegistryIterator &operator++() {
+    ++m_index;
+    return *this;
+  }
+  bool operator!=(const RegistryIterator &other) const {
+    return m_index != other.m_index;
+  }
+
+private:
+  Registry<T> *m_registry;
+  unsigned m_index;
+};
+
+template <typename T> class Registry : public CanBorrow {
+public:
+  T &at(unsigned i) LIFETIME_BOUND { return m_buffer[i]; }
+
+  void removeAt(unsigned i) { crashIfBorrowed(); }
+
+  RegistryIterator<T> begin() LIFETIME_BOUND {
+    return RegistryIterator<T>(*this, 0);
+  }
+  RegistryIterator<T> end() LIFETIME_BOUND {
+    return RegistryIterator<T>(*this, m_size);
+  }
+
+private:
+  T *m_buffer { nullptr };
+  unsigned m_size { 0 };
+};
+
+#endif

diff  --git 
a/clang/test/Analysis/Checkers/WebKit/unborrowed-local-vars-cxx23.cpp 
b/clang/test/Analysis/Checkers/WebKit/unborrowed-local-vars-cxx23.cpp
new file mode 100644
index 00000000000000..0f1ae4e58d6acd
--- /dev/null
+++ b/clang/test/Analysis/Checkers/WebKit/unborrowed-local-vars-cxx23.cpp
@@ -0,0 +1,43 @@
+// RUN: %clang_analyze_cc1 -std=c++23 
-analyzer-checker=alpha.webkit.UnborrowedLocalVarsChecker -verify %s
+
+#include "mock-canborrow.h"
+
+void someFunction();
+
+void borrow_function_get_loop(Vector<char> &vec) {
+  for (char &c : borrow(vec).get()) {
+    someFunction();
+    (void)c;
+  }
+}
+
+struct ReversedChars {
+  char *b;
+  char *e;
+  char *begin() const;
+  char *end() const;
+};
+struct ReverseAdaptor {};
+ReversedChars operator|(Vector<char> &vec, ReverseAdaptor);
+
+void borrow_get_pipe_loop(Vector<char> &vec) {
+  for (char &c : borrow(vec).get() | ReverseAdaptor()) {
+    someFunction();
+    (void)c;
+  }
+}
+
+void unguarded_pipe_loop(Vector<char> &vec) {
+  for (char &c : vec | ReverseAdaptor()) {
+    someFunction();
+    (void)c;
+  }
+}
+
+void reference_loop(Vector<char> &vec) {
+  for (char &c : 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;
+  }
+}

diff  --git a/clang/test/Analysis/Checkers/WebKit/unborrowed-local-vars.cpp 
b/clang/test/Analysis/Checkers/WebKit/unborrowed-local-vars.cpp
new file mode 100644
index 00000000000000..8de699f967146d
--- /dev/null
+++ b/clang/test/Analysis/Checkers/WebKit/unborrowed-local-vars.cpp
@@ -0,0 +1,656 @@
+// RUN: %clang_analyze_cc1 
-analyzer-checker=alpha.webkit.UnborrowedLocalVarsChecker -verify %s
+
+#include "mock-canborrow.h"
+
+void someFunction();
+
+Vector<char> makeVector();
+
+Vector<char> *getVectorPtr();
+Vector<char> &getVectorRef();
+
+namespace loan_shapes {
+void reference_loan(Vector<char> &vec) {
+  char &c = vec[0];
+  // 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 pointer_loan(Vector<char> &vec) {
+  char *p = 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]}}
+  someFunction();
+}
+
+void view_loan(Vector<char> &vec) {
+  StringView sv = vec.data();
+  // expected-warning@-1{{Local variable 'sv' is a loan on CanBorrow type 
'Vector<char>' that is not guarded by a Borrow 
[alpha.webkit.UnborrowedLocalVarsChecker]}}
+  someFunction();
+}
+
+void gsl_pointer_loan(Vector<char> &vec) {
+  CharSpan s = makeSpan(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 simple_container(SimpleContainer<char> &container) {
+  char &c = container[0];
+  // expected-warning@-1{{Local variable 'c' is a loan on CanBorrow type 
'SimpleContainer<char>' that is not guarded by a Borrow 
[alpha.webkit.UnborrowedLocalVarsChecker]}}
+  someFunction();
+}
+
+void two_hops(Vector<char> &vec) {
+  StringView sv = makeView(vec.data());
+  // expected-warning@-1{{Local variable 'sv' is a loan on CanBorrow type 
'Vector<char>' that is not guarded by a Borrow 
[alpha.webkit.UnborrowedLocalVarsChecker]}}
+  someFunction();
+}
+} // namespace loan_shapes
+
+namespace not_a_loan {
+void alias_reference(Vector<char> &vec) {
+  Vector<char> &r = vec;
+  someFunction();
+  r.append('x');
+}
+
+void alias_const_reference(Vector<char> &vec) {
+  const Vector<char> &r = vec;
+  someFunction();
+}
+
+void alias_pointer(Vector<char> &vec) {
+  Vector<char> *p = &vec;
+  someFunction();
+  p->append('x');
+}
+
+void value_copy(Vector<char> &vec) {
+  char c = vec[0];
+  someFunction();
+  (void)c;
+}
+
+void owner_accessor_chain(Owner<NotBorrowable> &owner) {
+  NotBorrowable *p = owner.get();
+  someFunction();
+  NotBorrowable &r = *owner;
+  someFunction();
+  char &c = owner->at(0);
+  someFunction();
+  (void)p;
+  (void)r;
+  (void)c;
+}
+
+void not_borrowable(NotBorrowable &n) {
+  char &c = n.at(0);
+  someFunction();
+  n.mutate();
+  (void)c;
+}
+
+void no_initializer() {
+  char *p;
+  someFunction();
+  (void)p;
+}
+} // namespace not_a_loan
+
+namespace lifetimebound_edge {
+void forwarded_reference(Vector<char> &someVec) {
+  Vector<char> &a = forwardRef(someVec);
+  // expected-warning@-1{{Local variable 'a' is a loan on CanBorrow type 
'Vector<char>' that is not guarded by a Borrow 
[alpha.webkit.UnborrowedLocalVarsChecker]}}
+  someFunction();
+}
+
+void forwarded_pointer(Vector<char> &someVec) {
+  Vector<char> *a = forwardPtr(someVec);
+  // expected-warning@-1{{Local variable 'a' is a loan on CanBorrow type 
'Vector<char>' that is not guarded by a Borrow 
[alpha.webkit.UnborrowedLocalVarsChecker]}}
+  someFunction();
+}
+
+void self_similar_container(Node &node) {
+  Node &child = node.firstChild();
+  // expected-warning@-1{{Local variable 'child' is a loan on CanBorrow type 
'Node' that is not guarded by a Borrow 
[alpha.webkit.UnborrowedLocalVarsChecker]}}
+  node.appendChild();
+  (void)child;
+}
+
+void self_similar_container_borrowed(Node &node) {
+  Borrow<Node> b(node);
+  Node &child = b.get().firstChild();
+  someFunction();
+  (void)child;
+}
+} // namespace lifetimebound_edge
+
+namespace guarded {
+void through_borrow(Vector<char> &vec) {
+  Borrow<Vector<char>> b(vec);
+  char *p = b->data();
+  someFunction();
+  (void)p;
+}
+
+void through_borrow_get(Vector<char> &vec) {
+  Borrow<Vector<char>> b(vec);
+  char &c = b.get()[0];
+  someFunction();
+  (void)c;
+}
+
+void through_borrow_conversion(Vector<char> &vec) {
+  Borrow<Vector<char>> b(vec);
+  char &c = static_cast<Vector<char> &>(b)[0];
+  someFunction();
+  (void)c;
+}
+
+void through_borrow_function(Vector<char> &vec) {
+  auto b = borrow(vec);
+  char &c = b.get()[0];
+  someFunction();
+  (void)c;
+}
+
+void view_through_borrow(Vector<char> &vec) {
+  Borrow<Vector<char>> b(vec);
+  StringView sv = b.get().data();
+  someFunction();
+  (void)sv;
+}
+
+void borrow_parameter(Borrow<Vector<char>> &b) {
+  char &c = b.get()[0];
+  someFunction();
+  (void)c;
+}
+} // namespace guarded
+
+namespace no_guardian_exemption {
+void borrow_in_enclosing_scope(Vector<char> &vec) {
+  Borrow<Vector<char>> b(vec);
+  {
+    char &c = vec[0];
+    // 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 const_reference_parameter(const Vector<char> &vec) {
+  const char &c = vec[0];
+  // 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 const_pointer_parameter(const Vector<char> *vec) {
+  const char *p = 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]}}
+  someFunction();
+}
+
+void local_container() {
+  Vector<char> vec;
+  char &c = vec[0];
+  // 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();
+  vec.append('x');
+}
+} // namespace no_guardian_exemption
+
+namespace nested_containers {
+void loan_on_outer(Vector<Vector<char>> &outer) {
+  Vector<char> &inner = outer[0];
+  // expected-warning@-1{{Local variable 'inner' is a loan on CanBorrow type 
'Vector<Vector<char>>' that is not guarded by a Borrow 
[alpha.webkit.UnborrowedLocalVarsChecker]}}
+  someFunction();
+}
+
+void borrow_on_outer(Vector<Vector<char>> &outer) {
+  Borrow<Vector<Vector<char>>> b(outer);
+  Vector<char> &inner = b.get()[0];
+  someFunction();
+  (void)inner;
+}
+
+void borrow_on_inner(Vector<Vector<char>> &outer) {
+  Borrow<Vector<Vector<char>>> outerBorrow(outer);
+  Vector<char> &inner = outerBorrow.get()[0];
+  Borrow<Vector<char>> innerBorrow(inner);
+  char &c = innerBorrow.get()[0];
+  someFunction();
+  (void)c;
+}
+
+void alias_on_outer(Vector<Vector<char>> &outer) {
+  Vector<Vector<char>> &r = outer;
+  someFunction();
+  (void)r;
+}
+} // namespace nested_containers
+
+namespace assignment_sink {
+void assign_loan(Vector<char> &vec) {
+  char *p = nullptr;
+  // expected-warning@-1{{Local variable 'p' is a loan on CanBorrow type 
'Vector<char>' that is not guarded by a Borrow 
[alpha.webkit.UnborrowedLocalVarsChecker]}}
+  p = vec.data();
+  someFunction();
+}
+
+void assign_guarded(Vector<char> &vec) {
+  Borrow<Vector<char>> b(vec);
+  char *p = nullptr;
+  p = b.get().data();
+  someFunction();
+  (void)p;
+}
+
+void assign_alias(Vector<char> &vec) {
+  Vector<char> *p = nullptr;
+  p = &vec;
+  someFunction();
+}
+} // namespace assignment_sink
+
+namespace conditional_origin {
+void one_unguarded_arm(Vector<char> &vec, bool flag) {
+  Borrow<Vector<char>> b(vec);
+  char *p = flag ? b.get().data() : 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]}}
+  someFunction();
+}
+
+void both_arms_guarded(Vector<char> &vec, Vector<char> &other, bool flag) {
+  Borrow<Vector<char>> b(vec);
+  Borrow<Vector<char>> c(other);
+  char *p = flag ? b.get().data() : c.get().data();
+  someFunction();
+  (void)p;
+}
+} // namespace conditional_origin
+
+namespace forked_trace {
+void call_one_unguarded(Vector<char> &vec, Borrow<Vector<char>> &b) {
+  const char *p = pick(b.get().data(), 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]}}
+  someFunction();
+}
+
+void call_both_guarded(Borrow<Vector<char>> &b, Borrow<Vector<char>> &c) {
+  const char *p = pick(b.get().data(), c.get().data());
+  someFunction();
+  (void)p;
+}
+
+void construct_one_unguarded(Vector<char> &vec, Borrow<Vector<char>> &b) {
+  TwoStringViews v(b.get().data(), vec.data());
+  // expected-warning@-1{{Local variable 'v' is a loan on CanBorrow type 
'Vector<char>' that is not guarded by a Borrow 
[alpha.webkit.UnborrowedLocalVarsChecker]}}
+  someFunction();
+}
+
+void construct_both_guarded(Borrow<Vector<char>> &b, Borrow<Vector<char>> &c) {
+  TwoStringViews v(b.get().data(), c.get().data());
+  someFunction();
+  (void)v;
+}
+} // namespace forked_trace
+
+namespace escape_paths {
+void out_parameter_by_reference(Vector<char> &vec, char *&out) {
+  out = vec.data();
+  // expected-warning@-1{{Parameter 'out' is a loan on CanBorrow type 
'Vector<char>' that is not guarded by a Borrow 
[alpha.webkit.UnborrowedLocalVarsChecker]}}
+  someFunction();
+}
+
+void outer_scope_local(Vector<char> &vec) {
+  char *p = nullptr;
+  // expected-warning@-1{{Local variable 'p' is a loan on CanBorrow type 
'Vector<char>' that is not guarded by a Borrow 
[alpha.webkit.UnborrowedLocalVarsChecker]}}
+  {
+    p = vec.data();
+    someFunction();
+  }
+  someFunction();
+  (void)p;
+}
+
+void borrow_outlives_assignment(Vector<char> &vec) {
+  Borrow<Vector<char>> b(vec);
+  char *p = nullptr;
+  {
+    p = b.get().data();
+    someFunction();
+  }
+  someFunction();
+  (void)p;
+}
+
+void store_through_out_pointer(Vector<char> &vec, char **out) {
+  *out = vec.data();
+  // expected-warning@-1{{Parameter 'out' is a loan on CanBorrow type 
'Vector<char>' that is not guarded by a Borrow 
[alpha.webkit.UnborrowedLocalVarsChecker]}}
+  someFunction();
+}
+
+void store_through_local_pointer(Vector<char> &vec) {
+  char *p = nullptr;
+  char **pp = &p;
+  // expected-warning@-1{{Local variable 'pp' is a loan on CanBorrow type 
'Vector<char>' that is not guarded by a Borrow 
[alpha.webkit.UnborrowedLocalVarsChecker]}}
+  *pp = vec.data();
+  someFunction();
+  (void)p;
+}
+
+void store_into_pointer_array(Vector<char> &vec, char *arr[]) {
+  arr[0] = vec.data();
+  // expected-warning@-1{{Parameter 'arr' is a loan on CanBorrow type 
'Vector<char>' that is not guarded by a Borrow 
[alpha.webkit.UnborrowedLocalVarsChecker]}}
+  someFunction();
+}
+
+void store_through_double_indirection(Vector<char> &vec, char ***out) {
+  **out = vec.data();
+  // expected-warning@-1{{Parameter 'out' is a loan on CanBorrow type 
'Vector<char>' that is not guarded by a Borrow 
[alpha.webkit.UnborrowedLocalVarsChecker]}}
+  someFunction();
+}
+
+void alias_through_out_reference(Vector<char> &vec, Vector<char> *&out) {
+  out = &vec;
+  someFunction();
+}
+
+void alias_through_out_pointer(Vector<char> &vec, Vector<char> **out) {
+  *out = &vec;
+  someFunction();
+}
+
+void guarded_store_through_out_pointer(Vector<char> &vec, char **out) {
+  Borrow<Vector<char>> b(vec);
+  *out = b.get().data();
+  someFunction();
+}
+} // namespace escape_paths
+
+namespace known_gaps {
+void unannotated_view_constructor(Vector<char> &vec) {
+  CharSpan s(vec.data());
+  someFunction();
+  (void)s;
+}
+
+void unannotated_function_parameter(Vector<char> &vec) {
+  CharSpan s = makeSpanUnannotated(vec);
+  someFunction();
+  (void)s;
+}
+
+void escape_from_trivial_block(Vector<char> &vec) {
+  char *p = nullptr;
+  {
+    p = vec.data();
+  }
+  someFunction();
+  (void)p;
+}
+
+void borrow_escapes_via_out_reference(Vector<char> &vec, char *&out) {
+  Borrow<Vector<char>> b(vec);
+  out = b.get().data();
+  someFunction();
+}
+
+void borrow_escapes_via_out_pointer(Vector<char> &vec, char **out) {
+  Borrow<Vector<char>> b(vec);
+  *out = b.get().data();
+  someFunction();
+}
+
+} // namespace known_gaps
+
+namespace range_for {
+void reference_loop(Vector<char> &vec) {
+  for (char &c : 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 auto_reference_loop(Vector<char> &vec) {
+  for (auto &c : 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 const_reference_loop(const Vector<char> &vec) {
+  for (const char &c : 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 nested_loop(Vector<Vector<char>> &outer) {
+  for (Vector<char> &inner : outer) {
+    // expected-warning@-1{{Local variable 'inner' is a loan on CanBorrow type 
'Vector<Vector<char>>' that is not guarded by a Borrow 
[alpha.webkit.UnborrowedLocalVarsChecker]}}
+    someFunction();
+    (void)inner;
+  }
+}
+
+void local_container_loop() {
+  Vector<char> vec;
+  for (char &c : 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 borrow_get_loop(Vector<char> &vec) {
+  Borrow<Vector<char>> b(vec);
+  for (char &c : b.get()) {
+    someFunction();
+    (void)c;
+  }
+}
+
+void borrow_parameter_loop(Borrow<Vector<char>> &b) {
+  for (char &c : b.get()) {
+    someFunction();
+    (void)c;
+  }
+}
+
+void value_loop(Vector<char> &vec) {
+  for (char c : vec) {
+    someFunction();
+    (void)c;
+  }
+}
+
+void array_loop() {
+  char arr[4];
+  for (char &c : arr) {
+    someFunction();
+    (void)c;
+  }
+}
+
+void temporary_range_loop() {
+  for (char &c : makeVector()) {
+    // 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 named_temporary_loop() {
+  Vector<char> &&r = makeVector();
+  for (char &c : r) {
+    // 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 returned_pointer_loop() {
+  for (char &c : *getVectorPtr()) {
+    // 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 returned_reference_loop() {
+  for (char &c : getVectorRef()) {
+    // 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 manual_iterator_loop(Vector<char> &vec) {
+  for (char *it = vec.begin(); it != vec.end(); ++it) {
+    // expected-warning@-1{{Local variable 'it' is a loan on CanBorrow type 
'Vector<char>' that is not guarded by a Borrow 
[alpha.webkit.UnborrowedLocalVarsChecker]}}
+    someFunction();
+    (void)*it;
+  }
+}
+
+void copy_into_local(Vector<char> &vec) {
+  char *first = vec.data();
+  // expected-warning@-1{{Local variable 'first' is a loan on CanBorrow type 
'Vector<char>' that is not guarded by a Borrow 
[alpha.webkit.UnborrowedLocalVarsChecker]}}
+  char *second = first;
+  someFunction();
+  (void)second;
+}
+} // namespace range_for
+
+namespace structured_bindings {
+struct PointerPair {
+  char *first;
+  char *second;
+};
+PointerPair getPair(Vector<char> &vec LIFETIME_BOUND);
+
+void from_lifetimebound_call(Vector<char> &vec) {
+  auto [a, b] = getPair(vec);
+  // expected-warning@-1{{Local variable 'a' is a loan on CanBorrow type 
'Vector<char>' that is not guarded by a Borrow 
[alpha.webkit.UnborrowedLocalVarsChecker]}}
+  // expected-warning@-2{{Local variable 'b' is a loan on CanBorrow type 
'Vector<char>' that is not guarded by a Borrow 
[alpha.webkit.UnborrowedLocalVarsChecker]}}
+  someFunction();
+}
+
+void through_borrow(Vector<char> &vec) {
+  Borrow<Vector<char>> b(vec);
+  auto [first, second] = getPair(b.get());
+  someFunction();
+  (void)first;
+  (void)second;
+}
+
+void per_element_sources(Vector<char> &vec, Vector<char> &other) {
+  Borrow<Vector<char>> b(other);
+  PointerPair p{b.get().data(), vec.data()};
+  auto [guarded, unguarded] = PointerPair{b.get().data(), vec.data()};
+  // expected-warning@-1{{Local variable 'unguarded' is a loan on CanBorrow 
type 'Vector<char>' that is not guarded by a Borrow 
[alpha.webkit.UnborrowedLocalVarsChecker]}}
+  someFunction();
+  (void)p;
+  (void)guarded;
+}
+
+void non_view_binding(Vector<char> &vec) {
+  struct SizeAndData {
+    unsigned size;
+    char *data;
+  };
+  auto [size, data] = SizeAndData{vec.size(), vec.data()};
+  // expected-warning@-1{{Local variable 'data' is a loan on CanBorrow type 
'Vector<char>' that is not guarded by a Borrow 
[alpha.webkit.UnborrowedLocalVarsChecker]}}
+  someFunction();
+  (void)size;
+}
+} // namespace structured_bindings
+
+namespace iterator_reach_back {
+
+Registry<char> makeRegistry();
+Registry<char> &namedRegistry();
+void use(char);
+
+void interior_destroyed_through_cursor() {
+  for (Cursor<char> c : makeRegistry()) {
+    // expected-warning@-1{{Local variable 'c' is a loan on CanBorrow type 
'Registry<char>' that is not guarded by a Borrow 
[alpha.webkit.UnborrowedLocalVarsChecker]}}
+    char &name = c.value();
+    c.remove();
+    use(name);
+  }
+}
+
+void interior_destroyed_named_container() {
+  for (Cursor<char> c : namedRegistry()) {
+    // expected-warning@-1{{Local variable 'c' is a loan on CanBorrow type 
'Registry<char>' that is not guarded by a Borrow 
[alpha.webkit.UnborrowedLocalVarsChecker]}}
+    char &name = c.value();
+    c.remove();
+    use(name);
+  }
+}
+
+void borrowed_temporary_is_silent() {
+  Registry<char> &&r = makeRegistry();
+  Borrow<Registry<char>> b(r);
+  for (Cursor<char> c : b.get()) {
+    char &name = c.value();
+    use(name);
+  }
+}
+} // namespace iterator_reach_back
+
+namespace dependent_initializers {
+
+template <typename T> void view_from_dependent_paren_init(T &source) {
+  StringView view(source.data());
+  // expected-warning@-1{{Local variable 'view' is a loan on CanBorrow type 
'Vector<char>' that is not guarded by a Borrow 
[alpha.webkit.UnborrowedLocalVarsChecker]}}
+  someFunction();
+}
+
+void instantiate_it(Vector<char> &vec) {
+  view_from_dependent_paren_init(vec);
+}
+
+template <typename T> void never_instantiated(T &source) {
+  StringView view(source.data());
+  someFunction();
+}
+
+} // namespace dependent_initializers
+
+namespace short_lived_temporaries {
+
+struct Key {
+  ~Key();
+};
+Key makeKey();
+unsigned indexFor(const Key &);
+
+void unrelated_temporary_is_still_a_loan(Vector<char> &vec) {
+  char &c = vec[indexFor(makeKey())];
+  // 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 unrelated_temporary_through_borrow(Vector<char> &vec) {
+  Borrow<Vector<char>> b(vec);
+  char &c = b.get()[indexFor(makeKey())];
+  someFunction();
+  (void)c;
+}
+
+void borrow_temporary_dies_with_the_statement(Vector<char> &vec) {
+  char &c = borrow(vec).get()[0];
+  // expected-warning@-1{{temporary bound to local reference 'c' will be 
destroyed at the end of the full-expression}}
+  // expected-warning@-2{{Local variable 'c' is a loan on CanBorrow type 
'Vector<char>' that is not guarded by a Borrow 
[alpha.webkit.UnborrowedLocalVarsChecker]}}
+  someFunction();
+  (void)c;
+}
+
+} // namespace short_lived_temporaries

diff  --git a/clang/test/Analysis/Checkers/WebKit/uncounted-local-vars.cpp 
b/clang/test/Analysis/Checkers/WebKit/uncounted-local-vars.cpp
index 656a755022d1eb..96ff48b9605b37 100644
--- a/clang/test/Analysis/Checkers/WebKit/uncounted-local-vars.cpp
+++ b/clang/test/Analysis/Checkers/WebKit/uncounted-local-vars.cpp
@@ -794,6 +794,13 @@ namespace binding_raw_ptr {
     a->method();
   }
 
+  void bind_temp_from_guarded(RefPtr<RefCountable> owner) {
+    auto [a, b] = pair<RefCountable*, RefCountable*> { owner.get(), 
owner.get() };
+    // expected-warning@-1{{Local variable 'a' is a raw reference to 
RefPtr-capable type 'binding_raw_ptr::pair<RefCountable *, RefCountable *>' 
[alpha.webkit.UncountedLocalVarsChecker]}}
+    // expected-warning@-2{{Local variable 'b' is a raw reference to 
RefPtr-capable type 'binding_raw_ptr::pair<RefCountable *, RefCountable *>' 
[alpha.webkit.UncountedLocalVarsChecker]}}
+    a->method();
+  }
+
   struct ptr_container {
     RefPtr<RefCountable> a;
     RefCountable* b;


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

Reply via email to