[clang] [WebKit Checkers] Add alpha.webkit.UnborrowedLocalVarsChecker (PR #225274)
via cfe-commits
cfe-commits at lists.llvm.org
Tue Sep 22 14:03:43 PDT 2026
https://github.com/geoffreygaren updated https://github.com/llvm/llvm-project/pull/225274
>From 7aba4de38313d1607fd583508466a2bbef9c8382 Mon Sep 17 00:00:00 2001
From: Geoff Garen <ggaren at apple.com>
Date: Tue, 25 Aug 2026 19:31:33 -0700
Subject: [PATCH] [WebKit Checkers] Add alpha.webkit.UnborrowedLocalVarsChecker
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
---
clang/docs/analyzer/checkers.md | 90 +++
.../clang/StaticAnalyzer/Checkers/Checkers.td | 4 +
.../Checkers/WebKit/ASTUtils.cpp | 228 +++++-
.../StaticAnalyzer/Checkers/WebKit/ASTUtils.h | 16 +-
.../Checkers/WebKit/PtrTypesSemantics.cpp | 39 ++
.../Checkers/WebKit/PtrTypesSemantics.h | 16 +
.../WebKit/RawPtrRefCallArgsChecker.cpp | 6 +-
.../WebKit/RawPtrRefLocalVarsChecker.cpp | 100 ++-
.../Checkers/WebKit/RawPtrRefSafetyModel.cpp | 102 ++-
.../Checkers/WebKit/RawPtrRefSafetyModel.h | 36 +-
.../Analysis/Checkers/WebKit/mock-canborrow.h | 220 ++++++
.../WebKit/unborrowed-local-vars-cxx23.cpp | 43 ++
.../Checkers/WebKit/unborrowed-local-vars.cpp | 656 ++++++++++++++++++
.../Checkers/WebKit/uncounted-local-vars.cpp | 7 +
14 files changed, 1500 insertions(+), 63 deletions(-)
create mode 100644 clang/test/Analysis/Checkers/WebKit/mock-canborrow.h
create mode 100644 clang/test/Analysis/Checkers/WebKit/unborrowed-local-vars-cxx23.cpp
create mode 100644 clang/test/Analysis/Checkers/WebKit/unborrowed-local-vars.cpp
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..150c64964d19f8 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,99 @@ 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 /*CrossedLifetimeBound*/)>
callback,
- bool OriginDependsOnFullExpressionTemporary) {
+ bool OriginDependsOnFullExpressionTemporary, bool CrossedLifetimeBound);
+
+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 CrossedLifetimeBound) {
+ for (const Expr *Arg : Args) {
+ if (!tryToFindPtrOriginImpl(
+ Arg, StopAtFirstRefCountedObj, /*FollowLifetimeBound=*/true,
+ isSafePtr, isSafePtrType, isSafeGlobalDecl, callback,
+ OriginDependsOnFullExpressionTemporary, CrossedLifetimeBound))
+ 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 /*CrossedLifetimeBound*/)>
+ callback,
+ bool OriginDependsOnFullExpressionTemporary, bool CrossedLifetimeBound) {
while (E) {
if (auto *DRE = dyn_cast<DeclRefExpr>(E)) {
if (auto *VD = dyn_cast_or_null<VarDecl>(DRE->getDecl())) {
@@ -39,10 +125,19 @@ static bool tryToFindPtrOriginImpl(
auto IsImmortal = safeGetName(VD) == "NSApp";
if (VD->hasGlobalStorage() && (IsImmortal || QT.isConstQualified()))
return callback(E, /*IsSafe=*/true,
- OriginDependsOnFullExpressionTemporary);
+ OriginDependsOnFullExpressionTemporary,
+ CrossedLifetimeBound);
if (VD->hasGlobalStorage() && isSafeGlobalDecl(VD))
return callback(E, /*IsSafe=*/true,
- OriginDependsOnFullExpressionTemporary);
+ OriginDependsOnFullExpressionTemporary,
+ CrossedLifetimeBound);
+
+ if (FollowLifetimeBound && VD->isImplicit() && VD->isLocalVarDecl()) {
+ if (auto *Init = VD->getInit()) {
+ E = Init;
+ continue;
+ }
+ }
}
}
if (auto *Cleanups = dyn_cast<ExprWithCleanups>(E)) {
@@ -63,14 +158,32 @@ static bool tryToFindPtrOriginImpl(
if (auto *C = tempExpr->getConstructor()) {
if (auto *Class = C->getParent(); Class && isSafePtr(Class))
return callback(E, /*IsSafe=*/true,
- OriginDependsOnFullExpressionTemporary);
+ OriginDependsOnFullExpressionTemporary,
+ CrossedLifetimeBound);
+
+ if (FollowLifetimeBound) {
+ SmallVector<const Expr *, 2> BoundArgs;
+ findLifetimeBoundArgs(tempExpr, BoundArgs);
+ if (!BoundArgs.empty())
+ CrossedLifetimeBound = true;
+ if (BoundArgs.size() == 1) {
+ E = BoundArgs.front();
+ continue;
+ }
+ if (BoundArgs.size() > 1)
+ return tryToFindPtrOriginOfEach(
+ BoundArgs, StopAtFirstRefCountedObj, isSafePtr, isSafePtrType,
+ isSafeGlobalDecl, callback,
+ OriginDependsOnFullExpressionTemporary, CrossedLifetimeBound);
+ }
break;
}
}
if (auto *TempExpr = dyn_cast<CXXUnresolvedConstructExpr>(E)) {
if (isSafePtrType(TempExpr->getTypeAsWritten()))
return callback(TempExpr, /*IsSafe=*/true,
- OriginDependsOnFullExpressionTemporary);
+ OriginDependsOnFullExpressionTemporary,
+ CrossedLifetimeBound);
}
if (auto *POE = dyn_cast<PseudoObjectExpr>(E)) {
if (auto *RF = POE->getResultExpr()) {
@@ -88,13 +201,16 @@ static bool tryToFindPtrOriginImpl(
}
if (auto *Expr = dyn_cast<ConditionalOperator>(E)) {
return tryToFindPtrOriginImpl(Expr->getTrueExpr(),
- StopAtFirstRefCountedObj, isSafePtr,
- isSafePtrType, isSafeGlobalDecl, callback,
- OriginDependsOnFullExpressionTemporary) &&
- tryToFindPtrOriginImpl(Expr->getFalseExpr(),
- StopAtFirstRefCountedObj, isSafePtr,
+ StopAtFirstRefCountedObj,
+ FollowLifetimeBound, isSafePtr,
isSafePtrType, isSafeGlobalDecl, callback,
- OriginDependsOnFullExpressionTemporary);
+ OriginDependsOnFullExpressionTemporary,
+ CrossedLifetimeBound) &&
+ tryToFindPtrOriginImpl(
+ Expr->getFalseExpr(), StopAtFirstRefCountedObj,
+ FollowLifetimeBound, isSafePtr, isSafePtrType,
+ isSafeGlobalDecl, callback,
+ OriginDependsOnFullExpressionTemporary, CrossedLifetimeBound);
}
if (auto *cast = dyn_cast<CastExpr>(E)) {
if (StopAtFirstRefCountedObj) {
@@ -102,11 +218,13 @@ static bool tryToFindPtrOriginImpl(
dyn_cast_or_null<FunctionDecl>(cast->getConversionFunction())) {
if (isCtorOfSafePtr(ConversionFunc))
return callback(E, /*IsSafe=*/true,
- OriginDependsOnFullExpressionTemporary);
+ OriginDependsOnFullExpressionTemporary,
+ CrossedLifetimeBound);
}
if (isa<CXXFunctionalCastExpr>(E) && isSafePtrType(cast->getType()))
return callback(E, /*IsSafe=*/true,
- OriginDependsOnFullExpressionTemporary);
+ OriginDependsOnFullExpressionTemporary,
+ CrossedLifetimeBound);
}
// FIXME: This can give false "origin" that would lead to false negatives
// in checkers. See https://reviews.llvm.org/D37023 for reference.
@@ -119,13 +237,15 @@ static bool tryToFindPtrOriginImpl(
Callee->hasAttr<NSReturnsRetainedAttr>() ||
Callee->hasAttr<NSReturnsAutoreleasedAttr>()) {
return callback(E, /*IsSafe=*/true,
- OriginDependsOnFullExpressionTemporary);
+ OriginDependsOnFullExpressionTemporary,
+ CrossedLifetimeBound);
}
}
if (isSafePtrType(call->getType()))
return callback(E, /*IsSafe=*/true,
- OriginDependsOnFullExpressionTemporary);
+ OriginDependsOnFullExpressionTemporary,
+ CrossedLifetimeBound);
if (auto *memberCall = dyn_cast<CXXMemberCallExpr>(call)) {
if (auto *decl = memberCall->getMethodDecl()) {
@@ -134,7 +254,8 @@ static bool tryToFindPtrOriginImpl(
E = memberCall->getImplicitObjectArgument();
if (StopAtFirstRefCountedObj) {
return callback(E, /*IsSafe=*/true,
- OriginDependsOnFullExpressionTemporary);
+ OriginDependsOnFullExpressionTemporary,
+ CrossedLifetimeBound);
}
continue;
}
@@ -164,7 +285,8 @@ static bool tryToFindPtrOriginImpl(
if (isCtorOfSafePtr(callee)) {
if (StopAtFirstRefCountedObj)
return callback(E, /*IsSafe=*/true,
- OriginDependsOnFullExpressionTemporary);
+ OriginDependsOnFullExpressionTemporary,
+ CrossedLifetimeBound);
E = call->getArg(0);
continue;
@@ -177,11 +299,13 @@ static bool tryToFindPtrOriginImpl(
if (isSafePtrType(callee->getReturnType()))
return callback(E, /*IsSafe=*/true,
- OriginDependsOnFullExpressionTemporary);
+ OriginDependsOnFullExpressionTemporary,
+ CrossedLifetimeBound);
if (isSingleton(callee))
return callback(E, /*IsSafe=*/true,
- OriginDependsOnFullExpressionTemporary);
+ OriginDependsOnFullExpressionTemporary,
+ CrossedLifetimeBound);
if (callee->isInStdNamespace() && safeGetName(callee) == "forward") {
E = call->getArg(0);
@@ -199,12 +323,14 @@ static bool tryToFindPtrOriginImpl(
Name == "NSStringFromClass" || Name == "NSClassFromString" ||
Name == "NSStringFromProtocol" || Name == "NSProtocolFromString")
return callback(E, /*IsSafe=*/true,
- OriginDependsOnFullExpressionTemporary);
+ OriginDependsOnFullExpressionTemporary,
+ CrossedLifetimeBound);
} 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,
+ CrossedLifetimeBound);
}
if (auto *MemberExpr = dyn_cast<CXXDependentScopeMemberExpr>(CalleeE)) {
@@ -213,7 +339,8 @@ static bool tryToFindPtrOriginImpl(
bool IsGetter = MemberName == "get" || MemberName == "ptr";
if (Base && isSafePtrType(Base->getType()) && IsGetter)
return callback(E, /*IsSafe=*/true,
- OriginDependsOnFullExpressionTemporary);
+ OriginDependsOnFullExpressionTemporary,
+ CrossedLifetimeBound);
}
}
@@ -229,38 +356,61 @@ static bool tryToFindPtrOriginImpl(
if (auto *CXX = dyn_cast<CXXRecordDecl>(RD->getDecl()))
if (isSafePtr(CXX))
return callback(E, /*IsSafe=*/true,
- OriginDependsOnFullExpressionTemporary);
+ OriginDependsOnFullExpressionTemporary,
+ CrossedLifetimeBound);
}
}
}
}
}
+
+ if (FollowLifetimeBound) {
+ SmallVector<const Expr *, 2> BoundArgs;
+ findLifetimeBoundArgs(call, BoundArgs);
+ if (!BoundArgs.empty())
+ CrossedLifetimeBound = true;
+ if (BoundArgs.size() == 1) {
+ E = BoundArgs.front();
+ continue;
+ }
+ if (BoundArgs.size() > 1)
+ return tryToFindPtrOriginOfEach(
+ BoundArgs, StopAtFirstRefCountedObj, isSafePtr, isSafePtrType,
+ isSafeGlobalDecl, callback,
+ OriginDependsOnFullExpressionTemporary, CrossedLifetimeBound);
+ }
}
if (auto *ObjCMsgExpr = dyn_cast<ObjCMessageExpr>(E)) {
if (auto *Method = ObjCMsgExpr->getMethodDecl()) {
if (isSafePtrType(Method->getReturnType()))
return callback(E, /*IsSafe=*/true,
- OriginDependsOnFullExpressionTemporary);
+ OriginDependsOnFullExpressionTemporary,
+ CrossedLifetimeBound);
}
auto Selector = ObjCMsgExpr->getSelector();
auto NameForFirstSlot = Selector.getNameForSlot(0);
if ((NameForFirstSlot == "class" || NameForFirstSlot == "superclass") &&
!Selector.getNumArgs())
return callback(E, /*IsSafe=*/true,
- OriginDependsOnFullExpressionTemporary);
+ OriginDependsOnFullExpressionTemporary,
+ CrossedLifetimeBound);
}
if (auto *ObjCProtocol = dyn_cast<ObjCProtocolExpr>(E))
return callback(ObjCProtocol, /*IsSafe=*/true,
- OriginDependsOnFullExpressionTemporary);
+ OriginDependsOnFullExpressionTemporary,
+ CrossedLifetimeBound);
if (auto *ObjCDict = dyn_cast<ObjCDictionaryLiteral>(E))
return callback(ObjCDict, /*IsSafe=*/true,
- OriginDependsOnFullExpressionTemporary);
+ OriginDependsOnFullExpressionTemporary,
+ CrossedLifetimeBound);
if (auto *ObjCArray = dyn_cast<ObjCArrayLiteral>(E))
return callback(ObjCArray, /*IsSafe=*/true,
- OriginDependsOnFullExpressionTemporary);
+ OriginDependsOnFullExpressionTemporary,
+ CrossedLifetimeBound);
if (auto *ObjCStr = dyn_cast<ObjCStringLiteral>(E))
return callback(ObjCStr, /*IsSafe=*/true,
- OriginDependsOnFullExpressionTemporary);
+ OriginDependsOnFullExpressionTemporary,
+ CrossedLifetimeBound);
if (auto *unaryOp = dyn_cast<UnaryOperator>(E)) {
// FIXME: Currently accepts ANY unary operator. Is it OK?
E = unaryOp->getSubExpr();
@@ -269,29 +419,33 @@ static bool tryToFindPtrOriginImpl(
if (auto *BoxedExpr = dyn_cast<ObjCBoxedExpr>(E)) {
if (StopAtFirstRefCountedObj)
return callback(BoxedExpr, /*IsSafe=*/true,
- OriginDependsOnFullExpressionTemporary);
+ OriginDependsOnFullExpressionTemporary,
+ CrossedLifetimeBound);
E = BoxedExpr->getSubExpr();
continue;
}
break;
}
// Some other expression.
- return callback(E, /*IsSafe=*/false, OriginDependsOnFullExpressionTemporary);
+ return callback(E, /*IsSafe=*/false, OriginDependsOnFullExpressionTemporary,
+ CrossedLifetimeBound);
}
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 /*CrossedLifetimeBound*/)>
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,
+ /*CrossedLifetimeBound=*/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..0e2449210bbfb5 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 /*CrossedLifetimeBound*/)>
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..c6193e12b9c13c 100644
--- a/clang/lib/StaticAnalyzer/Checkers/WebKit/PtrTypesSemantics.cpp
+++ b/clang/lib/StaticAnalyzer/Checkers/WebKit/PtrTypesSemantics.cpp
@@ -14,6 +14,7 @@
#include "clang/AST/DeclCXX.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 +132,42 @@ 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());
+}
+
+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 +186,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..179cef7fc10516 100644
--- a/clang/lib/StaticAnalyzer/Checkers/WebKit/PtrTypesSemantics.h
+++ b/clang/lib/StaticAnalyzer/Checkers/WebKit/PtrTypesSemantics.h
@@ -52,6 +52,19 @@ 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<> 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<>, false if not.
+bool isBorrow(const clang::CXXRecordDecl *Class);
+
+/// \returns true if \p T is a Borrow<>.
+bool isBorrowType(const 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 +155,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..aac6b8b2d70f24 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 CrossedLifetimeBound) {
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, CrossedLifetimeBound))
return true;
return false;
});
diff --git a/clang/lib/StaticAnalyzer/Checkers/WebKit/RawPtrRefLocalVarsChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/WebKit/RawPtrRefLocalVarsChecker.cpp
index 2d34ed9e4fae34..c158837c03810f 100644
--- a/clang/lib/StaticAnalyzer/Checkers/WebKit/RawPtrRefLocalVarsChecker.cpp
+++ b/clang/lib/StaticAnalyzer/Checkers/WebKit/RawPtrRefLocalVarsChecker.cpp
@@ -194,6 +194,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;
@@ -255,7 +274,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);
@@ -321,29 +344,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);
},
@@ -352,12 +394,18 @@ class RawPtrRefLocalVarsChecker
return Model->isSafeDecl(D, BR->getSourceManager());
},
[&](const clang::Expr *InitArgOrigin, bool IsSafe,
- bool OriginDependsOnFullExpressionTemporary) {
+ bool OriginDependsOnFullExpressionTemporary,
+ bool CrossedLifetimeBound) {
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;
@@ -374,12 +422,15 @@ class RawPtrRefLocalVarsChecker
if (EFA.isACallToEnsureFn(InitArgOrigin))
return true;
- if (Model->isSafeExpr(InitArgOrigin))
+ if (Model->isSafeExpr(InitArgOrigin, CrossedLifetimeBound))
return true;
- if (hasGuardian(V, InitArgOrigin, DeclWithIssue))
+ if (!Model->checksForInteriorDestruction() &&
+ hasGuardian(V, InitArgOrigin, DeclWithIssue))
return true;
+ if (!Origin)
+ Origin = InitArgOrigin;
return false;
});
}
@@ -425,7 +476,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);
@@ -434,7 +486,7 @@ class RawPtrRefLocalVarsChecker
Os << "Parameter ";
printQuotedQualifiedName(Os, V);
Os << " is a ";
- printPointerTypeAndType(Os, SinkType);
+ printPointerTypeAndType(Os, SinkType, Origin);
SourceLocation ExprLoc = (Value) ? Value->getExprLoc() : V->getLocation();
PathDiagnosticLocation BSLoc(ExprLoc, BR->getSourceManager());
@@ -457,7 +509,7 @@ class RawPtrRefLocalVarsChecker
else
printQuotedQualifiedName(Os, V);
Os << " is a ";
- printPointerTypeAndType(Os, SinkType);
+ printPointerTypeAndType(Os, SinkType, Origin);
PathDiagnosticLocation BSLoc(V->getLocation(), BR->getSourceManager());
auto Report = std::make_unique<BasicBugReport>(Bug, Os.str(), BSLoc);
@@ -467,8 +519,10 @@ class RawPtrRefLocalVarsChecker
}
}
- void printPointerTypeAndType(llvm::raw_svector_ostream &Os,
- QualType QT) const {
+ void printPointerTypeAndType(llvm::raw_svector_ostream &Os, QualType QT,
+ const Expr *Origin) const {
+ if (Model->describeHazard(Os, Origin, QT))
+ return;
auto *VarType = QT.getTypePtr();
auto *RTC = Model->retainTypeChecker();
if (RTC && isa<TypedefType>(VarType)) {
@@ -514,6 +568,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) {
@@ -539,3 +601,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..28e0e1bc5f9bac 100644
--- a/clang/lib/StaticAnalyzer/Checkers/WebKit/RawPtrRefSafetyModel.cpp
+++ b/clang/lib/StaticAnalyzer/Checkers/WebKit/RawPtrRefSafetyModel.cpp
@@ -8,7 +8,10 @@
#include "RawPtrRefSafetyModel.h"
#include "ASTUtils.h"
+#include "DiagOutputUtils.h"
#include "clang/AST/Decl.h"
+#include "clang/AST/DeclTemplate.h"
+#include "clang/AST/Expr.h"
#include "clang/AST/ExprObjC.h"
#include "clang/AST/Type.h"
#include "clang/Analysis/DomainSpecific/CocoaConventions.h"
@@ -55,7 +58,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 +83,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);
}
@@ -92,6 +95,97 @@ class RetainPtrSafetyModel : public PtrRefSafetyModel {
RetainTypeChecker *retainTypeChecker() const override { return &RTC; }
};
+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();
+}
+
+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 CrossedLifetimeBound) const override {
+ if (!CrossedLifetimeBound)
+ 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"; }
+
+ bool describeHazard(llvm::raw_ostream &Os, const Expr *Origin,
+ QualType) const override {
+ Os << "loan on ";
+ QualType OriginType = Origin ? pointeeType(Origin->getType()) : QualType();
+
+ if (!OriginType.isNull() && isBorrowType(OriginType)) {
+ QualType Borrowed = borrowedType(OriginType);
+ if (Borrowed.isNull())
+ Os << "a CanBorrow object";
+ else {
+ Os << "CanBorrow type ";
+ printTypeName(Os, Borrowed);
+ }
+ Os << " taken from a Borrow<> temporary that dies at the end of the "
+ "statement";
+ return true;
+ }
+
+ if (!OriginType.isNull() && OriginType->getAsRecordDecl()) {
+ Os << "CanBorrow type ";
+ printTypeName(Os, OriginType);
+ } else
+ Os << "a CanBorrow object";
+ Os << " that is not guarded by a Borrow<>";
+ return true;
+ }
+};
+
} // namespace
std::optional<bool> clang::isUnsafePtrForStorage(const PtrRefSafetyModel &Model,
@@ -115,3 +209,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..e8a35f219b7e31 100644
--- a/clang/lib/StaticAnalyzer/Checkers/WebKit/RawPtrRefSafetyModel.h
+++ b/clang/lib/StaticAnalyzer/Checkers/WebKit/RawPtrRefSafetyModel.h
@@ -10,6 +10,8 @@
#define LLVM_CLANG_ANALYZER_WEBKIT_RAWPTRREFSAFETYMODEL_H
#include "PtrTypesSemantics.h"
+#include "clang/AST/Type.h"
+#include "llvm/Support/raw_ostream.h"
#include <memory>
#include <optional>
#include <string>
@@ -37,7 +39,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 +56,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 CrossedLifetimeBound is whether the traversal that reached \p E
+ /// followed at least one [[clang::lifetimebound]] edge.
+ virtual bool isSafeExpr(const Expr *, bool CrossedLifetimeBound) 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 +84,16 @@ 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. \returns false to
+ /// let the caller print its default description.
+ virtual bool describeHazard(llvm::raw_ostream &Os, const Expr *Origin,
+ QualType SinkType) const {
+ return false;
+ }
+
/// \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 +115,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<>.
+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> ®istry LIFETIME_BOUND, unsigned index)
+ : m_registry(®istry), 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> ®istry LIFETIME_BOUND, unsigned index)
+ : m_registry(®istry), 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..387dc1b8a79361
--- /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 at -1{{Local variable 'c' is a loan on CanBorrow type 'Vector<char>' that is not guarded by a Borrow<> [alpha.webkit.UnborrowedLocalVarsChecker]}}
+ someFunction();
+ (void)c;
+ }
+}
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..1414e381f49704
--- /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 at -1{{Local variable 'c' is a loan on CanBorrow type 'Vector<char>' that is not guarded by a Borrow<> [alpha.webkit.UnborrowedLocalVarsChecker]}}
+ someFunction();
+}
+
+void pointer_loan(Vector<char> &vec) {
+ char *p = vec.data();
+ // expected-warning at -1{{Local variable 'p' is a loan on CanBorrow type 'Vector<char>' that is not guarded by a Borrow<> [alpha.webkit.UnborrowedLocalVarsChecker]}}
+ someFunction();
+}
+
+void view_loan(Vector<char> &vec) {
+ StringView sv = vec.data();
+ // expected-warning at -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 at -1{{Local variable 's' is a loan on CanBorrow type 'Vector<char>' that is not guarded by a Borrow<> [alpha.webkit.UnborrowedLocalVarsChecker]}}
+ someFunction();
+}
+
+void simple_container(SimpleContainer<char> &container) {
+ char &c = container[0];
+ // expected-warning at -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 at -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 at -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 at -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 at -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 at -1{{Local variable 'c' is a loan on CanBorrow type 'Vector<char>' that is not guarded by a Borrow<> [alpha.webkit.UnborrowedLocalVarsChecker]}}
+ someFunction();
+ }
+}
+
+void const_reference_parameter(const Vector<char> &vec) {
+ const char &c = vec[0];
+ // expected-warning at -1{{Local variable 'c' is a loan on CanBorrow type 'Vector<char>' that is not guarded by a Borrow<> [alpha.webkit.UnborrowedLocalVarsChecker]}}
+ someFunction();
+}
+
+void const_pointer_parameter(const Vector<char> *vec) {
+ const char *p = vec->data();
+ // expected-warning at -1{{Local variable 'p' is a loan on CanBorrow type 'Vector<char>' that is not guarded by a Borrow<> [alpha.webkit.UnborrowedLocalVarsChecker]}}
+ someFunction();
+}
+
+void local_container() {
+ Vector<char> vec;
+ char &c = vec[0];
+ // expected-warning at -1{{Local variable 'c' is a loan on CanBorrow type 'Vector<char>' that is not guarded by a Borrow<> [alpha.webkit.UnborrowedLocalVarsChecker]}}
+ someFunction();
+ 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 at -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 at -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 at -1{{Local variable 'p' is a loan on CanBorrow type 'Vector<char>' that is not guarded by a Borrow<> [alpha.webkit.UnborrowedLocalVarsChecker]}}
+ someFunction();
+}
+
+void 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 at -1{{Local variable 'p' is a loan on CanBorrow type 'Vector<char>' that is not guarded by a Borrow<> [alpha.webkit.UnborrowedLocalVarsChecker]}}
+ someFunction();
+}
+
+void 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 at -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 at -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 at -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 at -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 at -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 at -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 at -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 at -1{{Local variable 'c' is a loan on CanBorrow type 'Vector<char>' that is not guarded by a Borrow<> [alpha.webkit.UnborrowedLocalVarsChecker]}}
+ someFunction();
+ (void)c;
+ }
+}
+
+void auto_reference_loop(Vector<char> &vec) {
+ for (auto &c : vec) {
+ // expected-warning at -1{{Local variable 'c' is a loan on CanBorrow type 'Vector<char>' that is not guarded by a Borrow<> [alpha.webkit.UnborrowedLocalVarsChecker]}}
+ someFunction();
+ (void)c;
+ }
+}
+
+void const_reference_loop(const Vector<char> &vec) {
+ for (const char &c : vec) {
+ // expected-warning at -1{{Local variable 'c' is a loan on CanBorrow type 'Vector<char>' that is not guarded by a Borrow<> [alpha.webkit.UnborrowedLocalVarsChecker]}}
+ someFunction();
+ (void)c;
+ }
+}
+
+void nested_loop(Vector<Vector<char>> &outer) {
+ for (Vector<char> &inner : outer) {
+ // expected-warning at -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 at -1{{Local variable 'c' is a loan on CanBorrow type 'Vector<char>' that is not guarded by a Borrow<> [alpha.webkit.UnborrowedLocalVarsChecker]}}
+ someFunction();
+ (void)c;
+ }
+}
+
+void borrow_get_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 at -1{{Local variable 'c' is a loan on CanBorrow type 'Vector<char>' that is not guarded by a Borrow<> [alpha.webkit.UnborrowedLocalVarsChecker]}}
+ someFunction();
+ (void)c;
+ }
+}
+
+void named_temporary_loop() {
+ Vector<char> &&r = makeVector();
+ for (char &c : r) {
+ // expected-warning at -1{{Local variable 'c' is a loan on CanBorrow type 'Vector<char>' that is not guarded by a Borrow<> [alpha.webkit.UnborrowedLocalVarsChecker]}}
+ someFunction();
+ (void)c;
+ }
+}
+
+void returned_pointer_loop() {
+ for (char &c : *getVectorPtr()) {
+ // expected-warning at -1{{Local variable 'c' is a loan on CanBorrow type 'Vector<char>' that is not guarded by a Borrow<> [alpha.webkit.UnborrowedLocalVarsChecker]}}
+ someFunction();
+ (void)c;
+ }
+}
+
+void returned_reference_loop() {
+ for (char &c : getVectorRef()) {
+ // expected-warning at -1{{Local variable 'c' is a loan on CanBorrow type 'Vector<char>' that is not guarded by a Borrow<> [alpha.webkit.UnborrowedLocalVarsChecker]}}
+ someFunction();
+ (void)c;
+ }
+}
+
+void manual_iterator_loop(Vector<char> &vec) {
+ for (char *it = vec.begin(); it != vec.end(); ++it) {
+ // expected-warning at -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 at -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 at -1{{Local variable 'a' is a loan on CanBorrow type 'Vector<char>' that is not guarded by a Borrow<> [alpha.webkit.UnborrowedLocalVarsChecker]}}
+ // expected-warning at -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 at -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 at -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 at -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 at -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 at -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 at -1{{Local variable 'c' is a loan on CanBorrow type 'Vector<char>' that is not guarded by a Borrow<> [alpha.webkit.UnborrowedLocalVarsChecker]}}
+ someFunction();
+ (void)c;
+}
+
+void 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 at -1{{temporary bound to local reference 'c' will be destroyed at the end of the full-expression}}
+ // expected-warning at -2{{Local variable 'c' is a loan on CanBorrow type 'Vector<char>' taken from a Borrow<> temporary that dies at the end of the statement [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 2a3d9f2fefab8a..5c368cc240179f 100644
--- a/clang/test/Analysis/Checkers/WebKit/uncounted-local-vars.cpp
+++ b/clang/test/Analysis/Checkers/WebKit/uncounted-local-vars.cpp
@@ -710,6 +710,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 at -1{{Local variable 'a' is a raw reference to RefPtr-capable type 'binding_raw_ptr::pair<RefCountable *, RefCountable *>' [alpha.webkit.UncountedLocalVarsChecker]}}
+ // expected-warning at -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;
More information about the cfe-commits
mailing list