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