[clang] 081cc09 - [WebKit Checkers] Add alpha.webkit.UnborrowedLocalVarsChecker (#225274)
via cfe-commits
cfe-commits at lists.llvm.org
Tue Sep 22 22:27:35 PDT 2026
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> ®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..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 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..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 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>' 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 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