From 2cd97399e14a1c8de3037f8434704a8ac1efaa38 Mon Sep 17 00:00:00 2001 From: Ryosuke Niwa Date: Mon, 24 Feb 2025 12:14:14 -0800 Subject: [PATCH 01/31] [alpha.webkit.UnretainedLocalVarsChecker] Add a checker for local variables to NS and CF types. (#127554) This PR adds alpha.webkit.UnretainedLocalVarsChecker by generalizing RawPtrRefLocalVarsChecker. It checks local variables to NS or CF types are guarded with a RetainPtr or not. The new checker is effective for NS and CF types in Objective-C++ code without ARC, and it's effective for CF types in code with ARC. --- clang/docs/analyzer/checkers.rst | 45 +++ .../clang/StaticAnalyzer/Checkers/Checkers.td | 4 + .../Checkers/WebKit/ASTUtils.cpp | 8 +- .../StaticAnalyzer/Checkers/WebKit/ASTUtils.h | 2 + .../Checkers/WebKit/PtrTypesSemantics.cpp | 129 +++++- .../Checkers/WebKit/PtrTypesSemantics.h | 30 +- .../WebKit/RawPtrRefCallArgsChecker.cpp | 59 ++- .../WebKit/RawPtrRefLocalVarsChecker.cpp | 64 ++- .../WebKit/UncountedLambdaCapturesChecker.cpp | 5 +- .../Checkers/WebKit/objc-mock-types.h | 131 ++++++ .../WebKit/unretained-local-vars-arc.mm | 46 +++ .../Checkers/WebKit/unretained-local-vars.mm | 373 ++++++++++++++++++ 12 files changed, 856 insertions(+), 40 deletions(-) create mode 100644 clang/test/Analysis/Checkers/WebKit/objc-mock-types.h create mode 100644 clang/test/Analysis/Checkers/WebKit/unretained-local-vars-arc.mm create mode 100644 clang/test/Analysis/Checkers/WebKit/unretained-local-vars.mm diff --git a/clang/docs/analyzer/checkers.rst b/clang/docs/analyzer/checkers.rst index 459f4950d2a65..998d56995e776 100644 --- a/clang/docs/analyzer/checkers.rst +++ b/clang/docs/analyzer/checkers.rst @@ -3660,6 +3660,51 @@ Here are some examples of situations that we warn about as they *might* be poten RefCountable* uncounted = counted.get(); // warn } +alpha.webkit.UnretainedLocalVarsChecker +""""""""""""""""""""""""""""""""""""""" +The goal of this rule is to make sure that any NS or CF local variable is backed by a RetainPtr with lifetime that is strictly larger than the scope of the unretained local variable. To be on the safe side we require the scope of an unretained variable to be embedded in the scope of Retainptr object that backs it. + +The rules of when to use and not to use RetainPtr are same as alpha.webkit.UncountedCallArgsChecker for ref-counted objects. + +These are examples of cases that we consider safe: + + .. code-block:: cpp + + void foo1() { + RetainPtr retained; + // The scope of unretained is EMBEDDED in the scope of retained. + { + NSObject* unretained = retained.get(); // ok + } + } + + void foo2(RetainPtr retained_param) { + NSObject* unretained = retained_param.get(); // ok + } + + void FooClass::foo_method() { + NSObject* unretained = this; // ok + } + +Here are some examples of situations that we warn about as they *might* be potentially unsafe. The logic is that either we're able to guarantee that a local variable is safe or it's considered unsafe. + + .. code-block:: cpp + + void foo1() { + NSObject* unretained = [[NSObject alloc] init]; // warn + } + + NSObject* global_unretained; + void foo2() { + NSObject* unretained = global_unretained; // warn + } + + void foo3() { + RetainPtr retained; + // The scope of unretained is not EMBEDDED in the scope of retained. + NSObject* unretained = retained.get(); // warn + } + Debug Checkers --------------- diff --git a/clang/include/clang/StaticAnalyzer/Checkers/Checkers.td b/clang/include/clang/StaticAnalyzer/Checkers/Checkers.td index be94b7cd0d458..7a0a98a1cfd89 100644 --- a/clang/include/clang/StaticAnalyzer/Checkers/Checkers.td +++ b/clang/include/clang/StaticAnalyzer/Checkers/Checkers.td @@ -1811,4 +1811,8 @@ def UncheckedLocalVarsChecker : Checker<"UncheckedLocalVarsChecker">, HelpText<"Check unchecked local variables.">, Documentation; +def UnretainedLocalVarsChecker : Checker<"UnretainedLocalVarsChecker">, + HelpText<"Check unretained local variables.">, + Documentation; + } // end alpha.webkit diff --git a/clang/lib/StaticAnalyzer/Checkers/WebKit/ASTUtils.cpp b/clang/lib/StaticAnalyzer/Checkers/WebKit/ASTUtils.cpp index a12853d01819f..dc86c4fcc64b1 100644 --- a/clang/lib/StaticAnalyzer/Checkers/WebKit/ASTUtils.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/WebKit/ASTUtils.cpp @@ -24,6 +24,8 @@ bool isSafePtr(clang::CXXRecordDecl *Decl) { bool tryToFindPtrOrigin( const Expr *E, bool StopAtFirstRefCountedObj, + std::function isSafePtr, + std::function isSafePtrType, std::function callback) { while (E) { if (auto *tempExpr = dyn_cast(E)) { @@ -53,9 +55,9 @@ bool tryToFindPtrOrigin( } if (auto *Expr = dyn_cast(E)) { return tryToFindPtrOrigin(Expr->getTrueExpr(), StopAtFirstRefCountedObj, - callback) && + isSafePtr, isSafePtrType, callback) && tryToFindPtrOrigin(Expr->getFalseExpr(), StopAtFirstRefCountedObj, - callback); + isSafePtr, isSafePtrType, callback); } if (auto *cast = dyn_cast(E)) { if (StopAtFirstRefCountedObj) { @@ -92,7 +94,7 @@ bool tryToFindPtrOrigin( } if (auto *callee = call->getDirectCallee()) { - if (isCtorOfRefCounted(callee) || isCtorOfCheckedPtr(callee)) { + if (isCtorOfSafePtr(callee)) { if (StopAtFirstRefCountedObj) return callback(E, true); diff --git a/clang/lib/StaticAnalyzer/Checkers/WebKit/ASTUtils.h b/clang/lib/StaticAnalyzer/Checkers/WebKit/ASTUtils.h index b508043d0f37f..e2cc7b976adfc 100644 --- a/clang/lib/StaticAnalyzer/Checkers/WebKit/ASTUtils.h +++ b/clang/lib/StaticAnalyzer/Checkers/WebKit/ASTUtils.h @@ -54,6 +54,8 @@ class Expr; /// Returns false if any of calls to callbacks returned false. Otherwise true. bool tryToFindPtrOrigin( const clang::Expr *E, bool StopAtFirstRefCountedObj, + std::function isSafePtr, + std::function isSafePtrType, std::function 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 8340de9e5a7a9..7899b19854806 100644 --- a/clang/lib/StaticAnalyzer/Checkers/WebKit/PtrTypesSemantics.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/WebKit/PtrTypesSemantics.cpp @@ -8,11 +8,13 @@ #include "PtrTypesSemantics.h" #include "ASTUtils.h" +#include "clang/AST/Attr.h" #include "clang/AST/CXXInheritance.h" #include "clang/AST/Decl.h" #include "clang/AST/DeclCXX.h" #include "clang/AST/ExprCXX.h" #include "clang/AST/StmtVisitor.h" +#include "clang/Analysis/DomainSpecific/CocoaConventions.h" #include using namespace clang; @@ -117,6 +119,8 @@ bool isRefType(const std::string &Name) { Name == "RefPtr" || Name == "RefPtrAllowingPartiallyDestroyed"; } +bool isRetainPtr(const std::string &Name) { return Name == "RetainPtr"; } + bool isCheckedPtr(const std::string &Name) { return Name == "CheckedPtr" || Name == "CheckedRef"; } @@ -140,8 +144,14 @@ bool isCtorOfCheckedPtr(const clang::FunctionDecl *F) { return isCheckedPtr(safeGetName(F)); } +bool isCtorOfRetainPtr(const clang::FunctionDecl *F) { + const std::string &FunctionName = safeGetName(F); + return FunctionName == "RetainPtr" || FunctionName == "adoptNS" || + FunctionName == "adoptCF"; +} + bool isCtorOfSafePtr(const clang::FunctionDecl *F) { - return isCtorOfRefCounted(F) || isCtorOfCheckedPtr(F); + return isCtorOfRefCounted(F) || isCtorOfCheckedPtr(F) || isCtorOfRetainPtr(F); } template @@ -163,11 +173,15 @@ static bool isPtrOfType(const clang::QualType T, Predicate Pred) { return false; } -bool isSafePtrType(const clang::QualType T) { +bool isRefOrCheckedPtrType(const clang::QualType T) { return isPtrOfType( T, [](auto Name) { return isRefType(Name) || isCheckedPtr(Name); }); } +bool isRetainPtrType(const clang::QualType T) { + return isPtrOfType(T, [](auto Name) { return Name == "RetainPtr"; }); +} + bool isOwnerPtrType(const clang::QualType T) { return isPtrOfType(T, [](auto Name) { return isRefType(Name) || isCheckedPtr(Name) || Name == "unique_ptr" || @@ -195,6 +209,69 @@ std::optional isUnchecked(const QualType T) { return isUnchecked(T->getAsCXXRecordDecl()); } +void RetainTypeChecker::visitTranslationUnitDecl( + const TranslationUnitDecl *TUD) { + IsARCEnabled = TUD->getLangOpts().ObjCAutoRefCount; +} + +void RetainTypeChecker::visitTypedef(const TypedefDecl *TD) { + auto QT = TD->getUnderlyingType(); + if (!QT->isPointerType()) + return; + + auto PointeeQT = QT->getPointeeType(); + const RecordType *RT = PointeeQT->getAs(); + if (!RT) + return; + + for (auto *Redecl : RT->getDecl()->getMostRecentDecl()->redecls()) { + if (Redecl->getAttr()) { + CFPointees.insert(RT); + return; + } + } +} + +bool RetainTypeChecker::isUnretained(const QualType QT) { + if (ento::cocoa::isCocoaObjectRef(QT) && !IsARCEnabled) + return true; + auto CanonicalType = QT.getCanonicalType(); + auto PointeeType = CanonicalType->getPointeeType(); + auto *RT = dyn_cast_or_null(PointeeType.getTypePtrOrNull()); + return RT && CFPointees.contains(RT); +} + +std::optional isUnretained(const QualType T, bool IsARCEnabled) { + if (auto *Subst = dyn_cast(T)) { + if (auto *Decl = Subst->getAssociatedDecl()) { + if (isRetainPtr(safeGetName(Decl))) + return false; + } + } + if ((ento::cocoa::isCocoaObjectRef(T) && !IsARCEnabled) || + ento::coreFoundation::isCFObjectRef(T)) + return true; + + // RetainPtr strips typedef for CF*Ref. Manually check for struct __CF* types. + auto CanonicalType = T.getCanonicalType(); + auto *Type = CanonicalType.getTypePtrOrNull(); + if (!Type) + return false; + auto Pointee = Type->getPointeeType(); + auto *PointeeType = Pointee.getTypePtrOrNull(); + if (!PointeeType) + return false; + auto *Record = PointeeType->getAsStructureType(); + if (!Record) + return false; + auto *Decl = Record->getDecl(); + if (!Decl) + return false; + auto TypeName = Decl->getName(); + return TypeName.starts_with("__CF") || TypeName.starts_with("__CG") || + TypeName.starts_with("__CM"); +} + std::optional isUncounted(const CXXRecordDecl* Class) { // Keep isRefCounted first as it's cheaper. @@ -230,16 +307,20 @@ std::optional isUncheckedPtr(const QualType T) { return false; } -std::optional isUnsafePtr(const QualType T) { +std::optional isUnsafePtr(const QualType T, bool IsArcEnabled) { if (T->isPointerType() || T->isReferenceType()) { if (auto *CXXRD = T->getPointeeCXXRecordDecl()) { auto isUncountedPtr = isUncounted(CXXRD); auto isUncheckedPtr = isUnchecked(CXXRD); - if (isUncountedPtr && isUncheckedPtr) - return *isUncountedPtr || *isUncheckedPtr; + auto isUnretainedPtr = isUnretained(T, IsArcEnabled); + std::optional result; if (isUncountedPtr) - return *isUncountedPtr; - return isUncheckedPtr; + result = *isUncountedPtr; + if (isUncheckedPtr) + result = result ? *result || *isUncheckedPtr : *isUncheckedPtr; + if (isUnretainedPtr) + result = result ? *result || *isUnretainedPtr : *isUnretainedPtr; + return result; } } return false; @@ -248,6 +329,8 @@ std::optional isUnsafePtr(const QualType T) { std::optional isGetterOfSafePtr(const CXXMethodDecl *M) { assert(M); + std::optional RTC; + if (isa(M)) { const CXXRecordDecl *calleeMethodsClass = M->getParent(); auto className = safeGetName(calleeMethodsClass); @@ -263,16 +346,33 @@ std::optional isGetterOfSafePtr(const CXXMethodDecl *M) { method == "impl")) return true; + if (className == "RetainPtr" && method == "get") + return true; + // Ref -> T conversion // FIXME: Currently allowing any Ref -> whatever cast. if (isRefType(className)) { - if (auto *maybeRefToRawOperator = dyn_cast(M)) - return isUnsafePtr(maybeRefToRawOperator->getConversionType()); + if (auto *maybeRefToRawOperator = dyn_cast(M)) { + auto QT = maybeRefToRawOperator->getConversionType(); + auto *T = QT.getTypePtrOrNull(); + return T && (T->isPointerType() || T->isReferenceType()); + } } if (isCheckedPtr(className)) { - if (auto *maybeRefToRawOperator = dyn_cast(M)) - return isUnsafePtr(maybeRefToRawOperator->getConversionType()); + if (auto *maybeRefToRawOperator = dyn_cast(M)) { + auto QT = maybeRefToRawOperator->getConversionType(); + auto *T = QT.getTypePtrOrNull(); + return T && (T->isPointerType() || T->isReferenceType()); + } + } + + if (className == "RetainPtr") { + if (auto *maybeRefToRawOperator = dyn_cast(M)) { + auto QT = maybeRefToRawOperator->getConversionType(); + auto *T = QT.getTypePtrOrNull(); + return T && (T->isPointerType() || T->isReferenceType()); + } } } return false; @@ -297,6 +397,13 @@ bool isCheckedPtr(const CXXRecordDecl *R) { return false; } +bool isRetainPtr(const CXXRecordDecl *R) { + assert(R); + if (auto *TmplR = R->getTemplateInstantiationPattern()) + return safeGetName(TmplR) == "RetainPtr"; + return false; +} + bool isPtrConversion(const FunctionDecl *F) { assert(F); if (isCtorOfRefCounted(F)) diff --git a/clang/lib/StaticAnalyzer/Checkers/WebKit/PtrTypesSemantics.h b/clang/lib/StaticAnalyzer/Checkers/WebKit/PtrTypesSemantics.h index 9adfc0f1f5726..fcc1a41dba78b 100644 --- a/clang/lib/StaticAnalyzer/Checkers/WebKit/PtrTypesSemantics.h +++ b/clang/lib/StaticAnalyzer/Checkers/WebKit/PtrTypesSemantics.h @@ -11,6 +11,7 @@ #include "llvm/ADT/APInt.h" #include "llvm/ADT/DenseMap.h" +#include "llvm/ADT/DenseSet.h" #include "llvm/ADT/PointerUnion.h" #include @@ -21,8 +22,11 @@ class CXXRecordDecl; class Decl; class FunctionDecl; class QualType; +class RecordType; class Stmt; +class TranslationUnitDecl; class Type; +class TypedefDecl; // Ref-countability of a type is implicitly defined by Ref and RefPtr // implementation. It can be modeled as: type T having public methods ref() and @@ -51,6 +55,9 @@ bool isRefCounted(const clang::CXXRecordDecl *Class); /// \returns true if \p Class is a CheckedPtr / CheckedRef, false if not. bool isCheckedPtr(const clang::CXXRecordDecl *Class); +/// \returns true if \p Class is a RetainPtr, false if not. +bool isRetainPtr(const clang::CXXRecordDecl *Class); + /// \returns true if \p Class is ref-countable AND not ref-counted, false if /// not, std::nullopt if inconclusive. std::optional isUncounted(const clang::QualType T); @@ -59,6 +66,22 @@ std::optional isUncounted(const clang::QualType T); /// not, std::nullopt if inconclusive. std::optional isUnchecked(const clang::QualType T); +/// An inter-procedural analysis facility that detects CF types with the +/// underlying pointer type. +class RetainTypeChecker { + llvm::DenseSet CFPointees; + bool IsARCEnabled{false}; + +public: + void visitTranslationUnitDecl(const TranslationUnitDecl *); + void visitTypedef(const TypedefDecl *); + bool isUnretained(const QualType); +}; + +/// \returns true if \p Class is NS or CF objects AND not retained, false if +/// not, std::nullopt if inconclusive. +std::optional isUnretained(const clang::QualType T, bool IsARCEnabled); + /// \returns true if \p Class is ref-countable AND not ref-counted, false if /// not, std::nullopt if inconclusive. std::optional isUncounted(const clang::CXXRecordDecl* Class); @@ -77,11 +100,14 @@ std::optional isUncheckedPtr(const clang::QualType T); /// \returns true if \p T is either a raw pointer or reference to an uncounted /// or unchecked class, false if not, std::nullopt if inconclusive. -std::optional isUnsafePtr(const QualType T); +std::optional isUnsafePtr(const QualType T, bool IsArcEnabled); /// \returns true if \p T is a RefPtr, Ref, CheckedPtr, CheckedRef, or its /// variant, false if not. -bool isSafePtrType(const clang::QualType T); +bool isRefOrCheckedPtrType(const clang::QualType T); + +/// \returns true if \p T is a RetainPtr, false if not. +bool isRetainPtrType(const clang::QualType T); /// \returns true if \p T is a RefPtr, Ref, CheckedPtr, CheckedRef, or /// unique_ptr, false if not. diff --git a/clang/lib/StaticAnalyzer/Checkers/WebKit/RawPtrRefCallArgsChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/WebKit/RawPtrRefCallArgsChecker.cpp index f4ad549b8734a..aac8a4f100d17 100644 --- a/clang/lib/StaticAnalyzer/Checkers/WebKit/RawPtrRefCallArgsChecker.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/WebKit/RawPtrRefCallArgsChecker.cpp @@ -41,6 +41,8 @@ class RawPtrRefCallArgsChecker virtual std::optional isUnsafeType(QualType) const = 0; virtual std::optional isUnsafePtr(QualType) const = 0; + virtual bool isSafePtr(const CXXRecordDecl *Record) const = 0; + virtual bool isSafePtrType(const QualType type) const = 0; virtual const char *ptrKind() const = 0; void checkASTDecl(const TranslationUnitDecl *TUD, AnalysisManager &MGR, @@ -143,25 +145,28 @@ class RawPtrRefCallArgsChecker } bool isPtrOriginSafe(const Expr *Arg) const { - return tryToFindPtrOrigin(Arg, /*StopAtFirstRefCountedObj=*/true, - [&](const clang::Expr *ArgOrigin, bool IsSafe) { - if (IsSafe) - return true; - if (isa(ArgOrigin)) { - // foo(nullptr) - return true; - } - if (isa(ArgOrigin)) { - // FIXME: Check the value. - // foo(NULL) - return true; - } - if (isASafeCallArg(ArgOrigin)) - return true; - if (EFA.isACallToEnsureFn(ArgOrigin)) - return true; - return false; - }); + return tryToFindPtrOrigin( + Arg, /*StopAtFirstRefCountedObj=*/true, + [&](const clang::CXXRecordDecl *Record) { return isSafePtr(Record); }, + [&](const clang::QualType T) { return isSafePtrType(T); }, + [&](const clang::Expr *ArgOrigin, bool IsSafe) { + if (IsSafe) + return true; + if (isa(ArgOrigin)) { + // foo(nullptr) + return true; + } + if (isa(ArgOrigin)) { + // FIXME: Check the value. + // foo(NULL) + return true; + } + if (isASafeCallArg(ArgOrigin)) + return true; + if (EFA.isACallToEnsureFn(ArgOrigin)) + return true; + return false; + }); } bool shouldSkipCall(const CallExpr *CE) const { @@ -318,6 +323,14 @@ class UncountedCallArgsChecker final : public RawPtrRefCallArgsChecker { return isUncountedPtr(QT); } + bool isSafePtr(const CXXRecordDecl *Record) const final { + return isRefCounted(Record) || isCheckedPtr(Record); + } + + bool isSafePtrType(const QualType type) const final { + return isRefOrCheckedPtrType(type); + } + const char *ptrKind() const final { return "uncounted"; } }; @@ -335,6 +348,14 @@ class UncheckedCallArgsChecker final : public RawPtrRefCallArgsChecker { return isUncheckedPtr(QT); } + bool isSafePtr(const CXXRecordDecl *Record) const final { + return isRefCounted(Record) || isCheckedPtr(Record); + } + + bool isSafePtrType(const QualType type) const final { + return isRefOrCheckedPtrType(type); + } + const char *ptrKind() const final { return "unchecked"; } }; diff --git a/clang/lib/StaticAnalyzer/Checkers/WebKit/RawPtrRefLocalVarsChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/WebKit/RawPtrRefLocalVarsChecker.cpp index 6e9c8f1217a91..37eb6631d57d8 100644 --- a/clang/lib/StaticAnalyzer/Checkers/WebKit/RawPtrRefLocalVarsChecker.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/WebKit/RawPtrRefLocalVarsChecker.cpp @@ -14,6 +14,7 @@ #include "clang/AST/DeclCXX.h" #include "clang/AST/ParentMapContext.h" #include "clang/AST/RecursiveASTVisitor.h" +#include "clang/Analysis/DomainSpecific/CocoaConventions.h" #include "clang/Basic/SourceLocation.h" #include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h" #include "clang/StaticAnalyzer/Core/BugReporter/BugReporter.h" @@ -84,7 +85,7 @@ struct GuardianVisitor : public RecursiveASTVisitor { bool VisitCXXMemberCallExpr(const CXXMemberCallExpr *MCE) { auto MethodName = safeGetName(MCE->getMethodDecl()); if (MethodName == "swap" || MethodName == "leakRef" || - MethodName == "releaseNonNull") { + MethodName == "releaseNonNull" || MethodName == "clear") { auto *ThisArg = MCE->getImplicitObjectArgument()->IgnoreParenCasts(); if (auto *VarRef = dyn_cast(ThisArg)) { if (VarRef->getDecl() == Guardian) @@ -171,11 +172,16 @@ class RawPtrRefLocalVarsChecker mutable BugReporter *BR; EnsureFunctionAnalysis EFA; +protected: + mutable std::optional RTC; + public: RawPtrRefLocalVarsChecker(const char *description) : Bug(this, description, "WebKit coding guidelines") {} virtual std::optional isUnsafePtr(const QualType T) const = 0; + virtual bool isSafePtr(const CXXRecordDecl *) const = 0; + virtual bool isSafePtrType(const QualType) const = 0; virtual const char *ptrKind() const = 0; void checkASTDecl(const TranslationUnitDecl *TUD, AnalysisManager &MGR, @@ -208,6 +214,12 @@ class RawPtrRefLocalVarsChecker return Base::TraverseDecl(D); } + bool VisitTypedefDecl(TypedefDecl *TD) { + if (Checker->RTC) + Checker->RTC->visitTypedef(TD); + return true; + } + bool VisitVarDecl(VarDecl *V) { auto *Init = V->getInit(); if (Init && V->isLocalVarDecl()) @@ -257,6 +269,8 @@ class RawPtrRefLocalVarsChecker }; LocalVisitor visitor(this); + if (RTC) + RTC->visitTranslationUnitDecl(TUD); visitor.TraverseDecl(const_cast(TUD)); } @@ -269,6 +283,10 @@ class RawPtrRefLocalVarsChecker if (IsUncountedPtr && *IsUncountedPtr) { if (tryToFindPtrOrigin( Value, /*StopAtFirstRefCountedObj=*/false, + [&](const clang::CXXRecordDecl *Record) { + return isSafePtr(Record); + }, + [&](const clang::QualType Type) { return isSafePtrType(Type); }, [&](const clang::Expr *InitArgOrigin, bool IsSafe) { if (!InitArgOrigin || IsSafe) return true; @@ -298,8 +316,7 @@ class RawPtrRefLocalVarsChecker MaybeGuardianArgType->getAsCXXRecordDecl(); if (MaybeGuardianArgCXXRecord) { if (MaybeGuardian->isLocalVarDecl() && - (isRefCounted(MaybeGuardianArgCXXRecord) || - isCheckedPtr(MaybeGuardianArgCXXRecord) || + (isSafePtr(MaybeGuardianArgCXXRecord) || isRefcountedStringsHack(MaybeGuardian)) && isGuardedScopeEmbeddedInGuardianScope( V, MaybeGuardian)) @@ -324,6 +341,8 @@ class RawPtrRefLocalVarsChecker bool shouldSkipVarDecl(const VarDecl *V) const { assert(V); + if (isa(V)) + return true; return BR->getSourceManager().isInSystemHeader(V->getLocation()); } @@ -371,6 +390,12 @@ class UncountedLocalVarsChecker final : public RawPtrRefLocalVarsChecker { std::optional isUnsafePtr(const QualType T) const final { return isUncountedPtr(T); } + bool isSafePtr(const CXXRecordDecl *Record) const final { + return isRefCounted(Record) || isCheckedPtr(Record); + } + bool isSafePtrType(const QualType type) const final { + return isRefOrCheckedPtrType(type); + } const char *ptrKind() const final { return "uncounted"; } }; @@ -382,9 +407,34 @@ class UncheckedLocalVarsChecker final : public RawPtrRefLocalVarsChecker { std::optional isUnsafePtr(const QualType T) const final { return isUncheckedPtr(T); } + bool isSafePtr(const CXXRecordDecl *Record) const final { + return isRefCounted(Record) || isCheckedPtr(Record); + } + bool isSafePtrType(const QualType type) const final { + return isRefOrCheckedPtrType(type); + } const char *ptrKind() const final { return "unchecked"; } }; +class UnretainedLocalVarsChecker final : public RawPtrRefLocalVarsChecker { +public: + UnretainedLocalVarsChecker() + : RawPtrRefLocalVarsChecker("Unretained raw pointer or reference not " + "provably backed by a RetainPtr") { + RTC = RetainTypeChecker(); + } + std::optional isUnsafePtr(const QualType T) const final { + return RTC->isUnretained(T); + } + bool isSafePtr(const CXXRecordDecl *Record) const final { + return isRetainPtr(Record); + } + bool isSafePtrType(const QualType type) const final { + return isRetainPtrType(type); + } + const char *ptrKind() const final { return "unretained"; } +}; + } // namespace void ento::registerUncountedLocalVarsChecker(CheckerManager &Mgr) { @@ -402,3 +452,11 @@ void ento::registerUncheckedLocalVarsChecker(CheckerManager &Mgr) { bool ento::shouldRegisterUncheckedLocalVarsChecker(const CheckerManager &) { return true; } + +void ento::registerUnretainedLocalVarsChecker(CheckerManager &Mgr) { + Mgr.registerChecker(); +} + +bool ento::shouldRegisterUnretainedLocalVarsChecker(const CheckerManager &) { + return true; +} diff --git a/clang/lib/StaticAnalyzer/Checkers/WebKit/UncountedLambdaCapturesChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/WebKit/UncountedLambdaCapturesChecker.cpp index 3fd722c6df70a..c9aaf5d68ed81 100644 --- a/clang/lib/StaticAnalyzer/Checkers/WebKit/UncountedLambdaCapturesChecker.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/WebKit/UncountedLambdaCapturesChecker.cpp @@ -64,7 +64,8 @@ class UncountedLambdaCapturesChecker } bool shouldCheckThis() { - auto result = !ClsType.isNull() ? isUnsafePtr(ClsType) : std::nullopt; + auto result = + !ClsType.isNull() ? isUnsafePtr(ClsType, false) : std::nullopt; return result && *result; } @@ -308,7 +309,7 @@ class UncountedLambdaCapturesChecker if (ignoreParamVarDecl && isa(CapturedVar)) continue; QualType CapturedVarQualType = CapturedVar->getType(); - auto IsUncountedPtr = isUnsafePtr(CapturedVar->getType()); + auto IsUncountedPtr = isUnsafePtr(CapturedVar->getType(), false); if (IsUncountedPtr && *IsUncountedPtr) reportBug(C, CapturedVar, CapturedVarQualType); } else if (C.capturesThis() && shouldCheckThis) { diff --git a/clang/test/Analysis/Checkers/WebKit/objc-mock-types.h b/clang/test/Analysis/Checkers/WebKit/objc-mock-types.h new file mode 100644 index 0000000000000..7bb33bcb6cf44 --- /dev/null +++ b/clang/test/Analysis/Checkers/WebKit/objc-mock-types.h @@ -0,0 +1,131 @@ +@class NSString; +@class NSArray; +@class NSMutableArray; +#define CF_BRIDGED_TYPE(T) __attribute__((objc_bridge(T))) +typedef CF_BRIDGED_TYPE(id) void * CFTypeRef; +typedef signed long CFIndex; +typedef const struct __CFAllocator * CFAllocatorRef; +typedef const struct CF_BRIDGED_TYPE(NSString) __CFString * CFStringRef; +typedef const struct CF_BRIDGED_TYPE(NSArray) __CFArray * CFArrayRef; +typedef struct CF_BRIDGED_TYPE(NSMutableArray) __CFArray * CFMutableArrayRef; +extern const CFAllocatorRef kCFAllocatorDefault; +CFMutableArrayRef CFArrayCreateMutable(CFAllocatorRef allocator, CFIndex capacity); +extern void CFArrayAppendValue(CFMutableArrayRef theArray, const void *value); +CFArrayRef CFArrayCreate(CFAllocatorRef allocator, const void **values, CFIndex numValues); +CFIndex CFArrayGetCount(CFArrayRef theArray); +extern CFTypeRef CFRetain(CFTypeRef cf); +extern void CFRelease(CFTypeRef cf); + +__attribute__((objc_root_class)) +@interface NSObject ++ (instancetype) alloc; +- (instancetype) init; +- (instancetype)retain; +- (void)release; +@end + +@interface SomeObj : NSObject +- (void)doWork; +- (SomeObj *)other; +- (SomeObj *)next; +- (int)value; +@end + +@interface OtherObj : SomeObj +- (void)doMoreWork:(OtherObj *)other; +@end + +namespace WTF { + +template class RetainPtr; +template RetainPtr adoptNS(T*); +template RetainPtr adoptCF(T); + +template T *downcast(S *t) { return static_cast(t); } + +template struct RemovePointer { + typedef T Type; +}; + +template struct RemovePointer { + typedef T Type; +}; + +template struct RetainPtr { + using ValueType = typename RemovePointer::Type; + using PtrType = ValueType*; + PtrType t; + + RetainPtr() : t(nullptr) { } + + RetainPtr(PtrType t) + : t(t) { + if (t) + CFRetain(t); + } + RetainPtr(RetainPtr&& o) + : RetainPtr(o.t) + { + o.t = nullptr; + } + RetainPtr(const RetainPtr& o) + : RetainPtr(o.t) + { + } + RetainPtr operator=(const RetainPtr& o) + { + if (t) + CFRelease(t); + t = o.t; + if (t) + CFRetain(t); + return *this; + } + ~RetainPtr() { + clear(); + } + void clear() { + if (t) + CFRelease(t); + t = nullptr; + } + void swap(RetainPtr& o) { + PtrType tmp = t; + t = o.t; + o.t = tmp; + } + PtrType get() const { return t; } + PtrType operator->() const { return t; } + T &operator*() const { return *t; } + RetainPtr &operator=(PtrType t) { + RetainPtr o(t); + swap(o); + return *this; + } + operator PtrType() const { return t; } + operator bool() const { return t; } + +private: + template friend RetainPtr adoptNS(U*); + template friend RetainPtr adoptCF(U); + + enum AdoptTag { Adopt }; + RetainPtr(PtrType t, AdoptTag) : t(t) { } +}; + +template +RetainPtr adoptNS(T* t) { + return RetainPtr(t, RetainPtr::Adopt); +} + +template +RetainPtr adoptCF(T t) { + return RetainPtr(t, RetainPtr::Adopt); +} + +} + +using WTF::RetainPtr; +using WTF::adoptNS; +using WTF::adoptCF; +using WTF::downcast; diff --git a/clang/test/Analysis/Checkers/WebKit/unretained-local-vars-arc.mm b/clang/test/Analysis/Checkers/WebKit/unretained-local-vars-arc.mm new file mode 100644 index 0000000000000..92a718f7e3a4c --- /dev/null +++ b/clang/test/Analysis/Checkers/WebKit/unretained-local-vars-arc.mm @@ -0,0 +1,46 @@ +// RUN: %clang_analyze_cc1 -analyzer-checker=alpha.webkit.UnretainedLocalVarsChecker -fobjc-arc -verify %s + +#import "objc-mock-types.h" + +SomeObj *provide(); +void someFunction(); + +namespace raw_ptr { + +void foo() { + SomeObj *bar = [[SomeObj alloc] init]; + [bar doWork]; +} + +void foo2() { + SomeObj *bar = provide(); + [bar doWork]; +} + +void bar() { + CFMutableArrayRef array = CFArrayCreateMutable(kCFAllocatorDefault, 10); + // expected-warning@-1{{Local variable 'array' is unretained and unsafe [alpha.webkit.UnretainedLocalVarsChecker]}} + CFArrayAppendValue(array, nullptr); +} + +} // namespace raw_ptr + +@interface AnotherObj : NSObject +- (void)foo:(SomeObj *)obj; +@end + +@implementation AnotherObj +- (void)foo:(SomeObj*)obj { + SomeObj* obj2 = obj; + SomeObj* obj3 = provide(); + obj = nullptr; + [obj2 doWork]; + [obj3 doWork]; +} + +- (void)bar { + CFMutableArrayRef array = CFArrayCreateMutable(kCFAllocatorDefault, 10); + // expected-warning@-1{{Local variable 'array' is unretained and unsafe [alpha.webkit.UnretainedLocalVarsChecker]}} + CFArrayAppendValue(array, nullptr); +} +@end diff --git a/clang/test/Analysis/Checkers/WebKit/unretained-local-vars.mm b/clang/test/Analysis/Checkers/WebKit/unretained-local-vars.mm new file mode 100644 index 0000000000000..21ef6a5dca519 --- /dev/null +++ b/clang/test/Analysis/Checkers/WebKit/unretained-local-vars.mm @@ -0,0 +1,373 @@ +// RUN: %clang_analyze_cc1 -analyzer-checker=alpha.webkit.UnretainedLocalVarsChecker -verify %s + +#import "objc-mock-types.h" + +void someFunction(); + +namespace raw_ptr { +void foo() { + SomeObj *bar; + // FIXME: later on we might warn on uninitialized vars too +} + +void bar(SomeObj *) {} +} // namespace raw_ptr + +namespace pointer { +void foo_ref() { + SomeObj *bar = [[SomeObj alloc] init]; + // expected-warning@-1{{Local variable 'bar' is unretained and unsafe [alpha.webkit.UnretainedLocalVarsChecker]}} + [bar doWork]; +} + +bool bar_ref(SomeObj *obj) { + return !!obj; +} + +void cf_ptr() { + CFMutableArrayRef array = CFArrayCreateMutable(kCFAllocatorDefault, 10); + // expected-warning@-1{{Local variable 'array' is unretained and unsafe [alpha.webkit.UnretainedLocalVarsChecker]}} + CFArrayAppendValue(array, nullptr); +} +} // namespace pointer + +namespace guardian_scopes { +void foo1() { + RetainPtr foo; + { + SomeObj *bar = foo.get(); + } +} + +void foo2() { + RetainPtr foo; + // missing embedded scope here + SomeObj *bar = foo.get(); + // expected-warning@-1{{Local variable 'bar' is unretained and unsafe [alpha.webkit.UnretainedLocalVarsChecker]}} + [bar doWork]; +} + +void foo3() { + RetainPtr foo; + { + { SomeObj *bar = foo.get(); } + } +} + +void foo4() { + { + RetainPtr foo; + { SomeObj *bar = foo.get(); } + } +} + +struct SelfReferencingStruct { + SelfReferencingStruct* ptr; + SomeObj* obj { nullptr }; +}; + +void foo7(SomeObj* obj) { + SelfReferencingStruct bar = { &bar, obj }; + [bar.obj doWork]; +} + +void foo8(SomeObj* obj) { + RetainPtr foo; + + { + SomeObj *bar = foo.get(); + // expected-warning@-1{{Local variable 'bar' is unretained and unsafe [alpha.webkit.UnretainedLocalVarsChecker]}} + foo = nullptr; + [bar doWork]; + } + RetainPtr baz; + { + SomeObj *bar = baz.get(); + // expected-warning@-1{{Local variable 'bar' is unretained and unsafe [alpha.webkit.UnretainedLocalVarsChecker]}} + baz = obj; + [bar doWork]; + } + + foo = nullptr; + { + SomeObj *bar = foo.get(); + // No warning. It's okay to mutate RefPtr in an outer scope. + [bar doWork]; + } + foo = obj; + { + SomeObj *bar = foo.get(); + // expected-warning@-1{{Local variable 'bar' is unretained and unsafe [alpha.webkit.UnretainedLocalVarsChecker]}} + foo.clear(); + [bar doWork]; + } + { + SomeObj *bar = foo.get(); + // expected-warning@-1{{Local variable 'bar' is unretained and unsafe [alpha.webkit.UnretainedLocalVarsChecker]}} + foo = obj ? obj : nullptr; + [bar doWork]; + } + { + SomeObj *bar = [foo.get() other] ? foo.get() : nullptr; + // expected-warning@-1{{Local variable 'bar' is unretained and unsafe [alpha.webkit.UnretainedLocalVarsChecker]}} + foo = nullptr; + [bar doWork]; + } +} + +void foo9(SomeObj* o) { + RetainPtr guardian(o); + { + SomeObj *bar = guardian.get(); + // expected-warning@-1{{Local variable 'bar' is unretained and unsafe [alpha.webkit.UnretainedLocalVarsChecker]}} + guardian = o; // We don't detect that we're setting it to the same value. + [bar doWork]; + } + { + SomeObj *bar = guardian.get(); + // expected-warning@-1{{Local variable 'bar' is unretained and unsafe [alpha.webkit.UnretainedLocalVarsChecker]}} + RetainPtr other(bar); // We don't detect other has the same value as guardian. + guardian.swap(other); + [bar doWork]; + } + { + SomeObj *bar = guardian.get(); + // expected-warning@-1{{Local variable 'bar' is unretained and unsafe [alpha.webkit.UnretainedLocalVarsChecker]}} + RetainPtr other(static_cast&&>(guardian)); + [bar doWork]; + } + { + SomeObj *bar = guardian.get(); + // expected-warning@-1{{Local variable 'bar' is unretained and unsafe [alpha.webkit.UnretainedLocalVarsChecker]}} + guardian.clear(); + [bar doWork]; + } + { + SomeObj *bar = guardian.get(); + // expected-warning@-1{{Local variable 'bar' is unretained and unsafe [alpha.webkit.UnretainedLocalVarsChecker]}} + guardian = [o other] ? o : bar; + [bar doWork]; + } +} + +bool trivialFunction(CFMutableArrayRef array) { return !!array; } +void foo10() { + RetainPtr array = adoptCF(CFArrayCreateMutable(kCFAllocatorDefault, 10)); + { + CFMutableArrayRef arrayRef = array.get(); + CFArrayAppendValue(arrayRef, nullptr); + } + { + CFMutableArrayRef arrayRef = array.get(); + // expected-warning@-1{{Local variable 'arrayRef' is unretained and unsafe [alpha.webkit.UnretainedLocalVarsChecker]}} + array = nullptr; + CFArrayAppendValue(arrayRef, nullptr); + } + { + CFMutableArrayRef arrayRef = array.get(); + if (trivialFunction(arrayRef)) + arrayRef = nullptr; + } +} + +} // namespace guardian_scopes + +namespace auto_keyword { +class Foo { + SomeObj *provide_obj(); + CFMutableArrayRef provide_cf_array(); + void doWork(CFMutableArrayRef); + + void evil_func() { + SomeObj *bar = provide_obj(); + // expected-warning@-1{{Local variable 'bar' is unretained and unsafe [alpha.webkit.UnretainedLocalVarsChecker]}} + auto *baz = provide_obj(); + // expected-warning@-1{{Local variable 'baz' is unretained and unsafe [alpha.webkit.UnretainedLocalVarsChecker]}} + auto *baz2 = this->provide_obj(); + // expected-warning@-1{{Local variable 'baz2' is unretained and unsafe [alpha.webkit.UnretainedLocalVarsChecker]}} + [[clang::suppress]] auto *baz_suppressed = provide_obj(); // no-warning + } + + void func() { + SomeObj *bar = provide_obj(); + // expected-warning@-1{{Local variable 'bar' is unretained and unsafe [alpha.webkit.UnretainedLocalVarsChecker]}} + if (bar) + [bar doWork]; + } + + void bar() { + auto bar = provide_cf_array(); + // expected-warning@-1{{Local variable 'bar' is unretained and unsafe [alpha.webkit.UnretainedLocalVarsChecker]}} + doWork(bar); + [[clang::suppress]] auto baz = provide_cf_array(); // no-warning + doWork(baz); + } + +}; +} // namespace auto_keyword + +namespace guardian_casts { +void foo1() { + RetainPtr foo; + { + SomeObj *bar = downcast(foo.get()); + [bar doWork]; + } +} + +void foo2() { + RetainPtr foo; + { + SomeObj *bar = static_cast(downcast(foo.get())); + someFunction(); + } +} +} // namespace guardian_casts + +namespace conditional_op { +SomeObj *provide_obj(); +bool bar(); + +void foo() { + SomeObj *a = bar() ? nullptr : provide_obj(); + // expected-warning@-1{{Local variable 'a' is unretained and unsafe [alpha.webkit.UnretainedLocalVarsChecker]}} + RetainPtr b = provide_obj(); + { + SomeObj* c = bar() ? nullptr : b.get(); + [c doWork]; + SomeObj* d = bar() ? b.get() : nullptr; + [d doWork]; + } +} + +} // namespace conditional_op + +namespace local_assignment_basic { + +SomeObj *provide_obj(); + +void foo(SomeObj* a) { + SomeObj* b = a; + // expected-warning@-1{{Local variable 'b' is unretained and unsafe [alpha.webkit.UnretainedLocalVarsChecker]}} + if ([b other]) + b = provide_obj(); +} + +void bar(SomeObj* a) { + SomeObj* b; + // expected-warning@-1{{Local variable 'b' is unretained and unsafe [alpha.webkit.UnretainedLocalVarsChecker]}} + b = provide_obj(); +} + +void baz() { + RetainPtr a = provide_obj(); + { + SomeObj* b = a.get(); + // expected-warning@-1{{Local variable 'b' is unretained and unsafe [alpha.webkit.UnretainedLocalVarsChecker]}} + b = provide_obj(); + } +} + +} // namespace local_assignment_basic + +namespace local_assignment_to_parameter { + +SomeObj *provide_obj(); +void someFunction(); + +void foo(SomeObj* a) { + a = provide_obj(); + // expected-warning@-1{{Assignment to an unretained parameter 'a' is unsafe [alpha.webkit.UnretainedLocalVarsChecker]}} + someFunction(); + [a doWork]; +} + +CFMutableArrayRef provide_cf_array(); +void doWork(CFMutableArrayRef); + +void bar(CFMutableArrayRef a) { + a = provide_cf_array(); + // expected-warning@-1{{Assignment to an unretained parameter 'a' is unsafe [alpha.webkit.UnretainedLocalVarsChecker]}} + doWork(a); +} + +} // namespace local_assignment_to_parameter + +namespace local_assignment_to_static_local { + +SomeObj *provide_obj(); +void someFunction(); + +void foo() { + static SomeObj* a = nullptr; + // expected-warning@-1{{Static local variable 'a' is unretained and unsafe [alpha.webkit.UnretainedLocalVarsChecker]}} + a = provide_obj(); + someFunction(); + [a doWork]; +} + +CFMutableArrayRef provide_cf_array(); +void doWork(CFMutableArrayRef); + +void bar() { + static CFMutableArrayRef a = nullptr; + // expected-warning@-1{{Static local variable 'a' is unretained and unsafe [alpha.webkit.UnretainedLocalVarsChecker]}} + a = provide_cf_array(); + doWork(a); +} + +} // namespace local_assignment_to_static_local + +namespace local_assignment_to_global { + +SomeObj *provide_obj(); +void someFunction(); + +SomeObj* g_a = nullptr; +// expected-warning@-1{{Global variable 'local_assignment_to_global::g_a' is unretained and unsafe [alpha.webkit.UnretainedLocalVarsChecker]}} + +void foo() { + g_a = provide_obj(); + someFunction(); + [g_a doWork]; +} + +CFMutableArrayRef provide_cf_array(); +void doWork(CFMutableArrayRef); + +CFMutableArrayRef g_b = nullptr; +// expected-warning@-1{{Global variable 'local_assignment_to_global::g_b' is unretained and unsafe [alpha.webkit.UnretainedLocalVarsChecker]}} + +void bar() { + g_b = provide_cf_array(); + doWork(g_b); +} + +} // namespace local_assignment_to_global + +namespace local_var_for_singleton { + SomeObj *singleton(); + SomeObj *otherSingleton(); + void foo() { + SomeObj* bar = singleton(); + SomeObj* baz = otherSingleton(); + } + + CFMutableArrayRef cfSingleton(); + void bar() { + CFMutableArrayRef cf = cfSingleton(); + } +} + +bool doMoreWorkOpaque(OtherObj*); + +@implementation OtherObj +- (instancetype)init { + self = [super init]; + return self; +} + +- (void)doMoreWork:(OtherObj *)other { + doMoreWorkOpaque(other); +} +@end \ No newline at end of file From 2570f7312383ce49281c19a91c8f3b7705c723c7 Mon Sep 17 00:00:00 2001 From: Oliver Hunt Date: Sun, 2 Mar 2025 20:30:06 -0800 Subject: [PATCH 02/31] [analyzer] Handle structured bindings in alpha.webkit.UncountedCallArgsChecker (#129424) Simply add awareness of BindingDecl to the logic for identifying local assignments. --- .../Checkers/WebKit/ASTUtils.cpp | 7 +++- .../Checkers/WebKit/binding-to-refptr.cpp | 36 +++++++++++++++++++ 2 files changed, 42 insertions(+), 1 deletion(-) create mode 100644 clang/test/Analysis/Checkers/WebKit/binding-to-refptr.cpp diff --git a/clang/lib/StaticAnalyzer/Checkers/WebKit/ASTUtils.cpp b/clang/lib/StaticAnalyzer/Checkers/WebKit/ASTUtils.cpp index dc86c4fcc64b1..58020ec4e084d 100644 --- a/clang/lib/StaticAnalyzer/Checkers/WebKit/ASTUtils.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/WebKit/ASTUtils.cpp @@ -140,9 +140,14 @@ bool tryToFindPtrOrigin( bool isASafeCallArg(const Expr *E) { assert(E); if (auto *Ref = dyn_cast(E)) { - if (auto *D = dyn_cast_or_null(Ref->getFoundDecl())) { + auto *FoundDecl = Ref->getFoundDecl(); + if (auto *D = dyn_cast_or_null(FoundDecl)) { if (isa(D) || D->isLocalVarDecl()) return true; + } else if (auto *BD = dyn_cast_or_null(FoundDecl)) { + VarDecl *VD = BD->getHoldingVar(); + if (VD && (isa(VD) || VD->isLocalVarDecl())) + return true; } } if (isConstOwnerPtrMemberExpr(E)) diff --git a/clang/test/Analysis/Checkers/WebKit/binding-to-refptr.cpp b/clang/test/Analysis/Checkers/WebKit/binding-to-refptr.cpp new file mode 100644 index 0000000000000..012868fcb329a --- /dev/null +++ b/clang/test/Analysis/Checkers/WebKit/binding-to-refptr.cpp @@ -0,0 +1,36 @@ +// RUN: %clang_analyze_cc1 -analyzer-checker=alpha.webkit.UncountedCallArgsChecker -verify %s -std=c++2c +// RUN: %clang_analyze_cc1 -analyzer-checker=alpha.webkit.UncheckedLocalVarsChecker -verify %s -std=c++2c + +// expected-no-diagnostics + +#include "mock-types.h" + +class Node { +public: + Node* nextSibling() const; + + void ref() const; + void deref() const; +}; + +template struct pair { + A a; + B b; + template requires ( I == 0 ) A& get(); + template requires ( I == 1 ) B& get(); +}; + +namespace std { + template struct tuple_size; + template struct tuple_element; + template struct tuple_size<::pair> { static constexpr int value = 2; }; + template struct tuple_element<0, ::pair> { using type = A; }; + template struct tuple_element<1, ::pair> { using type = B; }; +} + +pair, RefPtr> &getPair(); + +static void testUnpackedAssignment() { + auto [a, b] = getPair(); + a->nextSibling(); +} From e31b22990c6c0eb377d19d729c04def0f9fe96b7 Mon Sep 17 00:00:00 2001 From: Ryosuke Niwa Date: Fri, 7 Mar 2025 14:40:33 -0800 Subject: [PATCH 03/31] [alpha.webkit.UncountedCallArgsChecker] Recognize CXXUnresolvedConstructExpr as a safe origin. (#130258) Handle CXXUnresolvedConstructExpr in tryToFindPtrOrigin so that constructing Ref, RefPtr, CheckedRef, CheckedPtr, ... constructed in such a way that its type is unresolved at AST level will be still treated as a safe pointer origin. Also fix a bug in isPtrOfType that it was not recognizing DeducedTemplateSpecializationType. --- .../Checkers/WebKit/ASTUtils.cpp | 4 +++ .../Checkers/WebKit/PtrTypesSemantics.cpp | 15 ++++---- .../Analysis/Checkers/WebKit/call-args.cpp | 35 +++++++++++++++++++ 3 files changed, 47 insertions(+), 7 deletions(-) diff --git a/clang/lib/StaticAnalyzer/Checkers/WebKit/ASTUtils.cpp b/clang/lib/StaticAnalyzer/Checkers/WebKit/ASTUtils.cpp index 58020ec4e084d..c8151e932997e 100644 --- a/clang/lib/StaticAnalyzer/Checkers/WebKit/ASTUtils.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/WebKit/ASTUtils.cpp @@ -43,6 +43,10 @@ bool tryToFindPtrOrigin( break; } } + if (auto *TempExpr = dyn_cast(E)) { + if (isSafePtrType(TempExpr->getTypeAsWritten())) + return callback(TempExpr, true); + } if (auto *POE = dyn_cast(E)) { if (auto *RF = POE->getResultExpr()) { E = RF; diff --git a/clang/lib/StaticAnalyzer/Checkers/WebKit/PtrTypesSemantics.cpp b/clang/lib/StaticAnalyzer/Checkers/WebKit/PtrTypesSemantics.cpp index 7899b19854806..8a304a07296fc 100644 --- a/clang/lib/StaticAnalyzer/Checkers/WebKit/PtrTypesSemantics.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/WebKit/PtrTypesSemantics.cpp @@ -162,13 +162,14 @@ static bool isPtrOfType(const clang::QualType T, Predicate Pred) { type = elaboratedT->desugar(); continue; } - auto *SpecialT = type->getAs(); - if (!SpecialT) - return false; - auto *Decl = SpecialT->getTemplateName().getAsTemplateDecl(); - if (!Decl) - return false; - return Pred(Decl->getNameAsString()); + if (auto *SpecialT = type->getAs()) { + auto *Decl = SpecialT->getTemplateName().getAsTemplateDecl(); + return Decl && Pred(Decl->getNameAsString()); + } else if (auto *DTS = type->getAs()) { + auto *Decl = DTS->getTemplateName().getAsTemplateDecl(); + return Decl && Pred(Decl->getNameAsString()); + } else + break; } return false; } diff --git a/clang/test/Analysis/Checkers/WebKit/call-args.cpp b/clang/test/Analysis/Checkers/WebKit/call-args.cpp index b4613d5090f29..e7afd9798da3e 100644 --- a/clang/test/Analysis/Checkers/WebKit/call-args.cpp +++ b/clang/test/Analysis/Checkers/WebKit/call-args.cpp @@ -359,6 +359,41 @@ namespace call_with_ptr_on_ref { } } +namespace call_with_explicit_construct_from_auto { + + struct Impl { + void ref() const; + void deref() const; + + static Ref create(); + }; + + template + struct ArgObj { + T* t; + }; + + struct Object { + Object(); + Object(Ref&&); + + Impl* impl() const { return m_impl.get(); } + + static Object create(ArgObj&) { return Impl::create(); } + static void bar(Impl&); + + private: + RefPtr m_impl; + }; + + template void foo() + { + auto result = Object::create(ArgObj { }); + Object::bar(Ref { *result.impl() }); + } + +} + namespace call_with_explicit_temporary_obj { void foo() { Ref { *provide() }->method(); From 7314aa91548ac64ab52c40c1629b52d8e7eca576 Mon Sep 17 00:00:00 2001 From: Ryosuke Niwa Date: Sun, 9 Mar 2025 14:59:46 -0700 Subject: [PATCH 04/31] [alpha.webkit.UnretainedLambdaCapturesChecker] Add a WebKit checker for lambda capturing NS or CF types. (#128651) Add a new WebKit checker for checking that lambda captures of CF types use RetainPtr either when ARC is disabled or enabled, and those of NS types use RetainPtr when ARC is disabled. --- clang/docs/analyzer/checkers.rst | 12 + .../clang/StaticAnalyzer/Checkers/Checkers.td | 4 + .../StaticAnalyzer/Checkers/CMakeLists.txt | 4 +- ...cpp => RawPtrRefLambdaCapturesChecker.cpp} | 134 ++++++-- .../Checkers/WebKit/objc-mock-types.h | 146 ++++++++- ...mbda-captures-decl-protects-this-crash.cpp | 4 +- .../WebKit/uncounted-lambda-captures.cpp | 34 +- .../WebKit/unretained-lambda-captures-arc.mm | 273 ++++++++++++++++ .../WebKit/unretained-lambda-captures.mm | 296 ++++++++++++++++++ .../lib/StaticAnalyzer/Checkers/BUILD.gn | 2 +- 10 files changed, 847 insertions(+), 62 deletions(-) rename clang/lib/StaticAnalyzer/Checkers/WebKit/{UncountedLambdaCapturesChecker.cpp => RawPtrRefLambdaCapturesChecker.cpp} (76%) create mode 100644 clang/test/Analysis/Checkers/WebKit/unretained-lambda-captures-arc.mm create mode 100644 clang/test/Analysis/Checkers/WebKit/unretained-lambda-captures.mm diff --git a/clang/docs/analyzer/checkers.rst b/clang/docs/analyzer/checkers.rst index 998d56995e776..4274acffb053b 100644 --- a/clang/docs/analyzer/checkers.rst +++ b/clang/docs/analyzer/checkers.rst @@ -3479,6 +3479,18 @@ Raw pointers and references to an object which supports CheckedPtr or CheckedRef See `WebKit Guidelines for Safer C++ Programming `_ for details. +alpha.webkit.UnretainedLambdaCapturesChecker +"""""""""""""""""""""""""""""""""""""""""""" +Raw pointers and references to NS or CF types can't be captured in lambdas. Only RetainPtr is allowed for CF types regardless of whether ARC is enabled or disabled, and only RetainPtr is allowed for NS types when ARC is disabled. + +.. code-block:: cpp + + void foo(NSObject *a, NSObject *b) { + [&, a](){ // warn about 'a' + do_something(b); // warn about 'b' + }; + }; + .. _alpha-webkit-UncountedCallArgsChecker: alpha.webkit.UncountedCallArgsChecker diff --git a/clang/include/clang/StaticAnalyzer/Checkers/Checkers.td b/clang/include/clang/StaticAnalyzer/Checkers/Checkers.td index 7a0a98a1cfd89..88a5a52e81a45 100644 --- a/clang/include/clang/StaticAnalyzer/Checkers/Checkers.td +++ b/clang/include/clang/StaticAnalyzer/Checkers/Checkers.td @@ -1795,6 +1795,10 @@ def NoUncheckedPtrMemberChecker : Checker<"NoUncheckedPtrMemberChecker">, HelpText<"Check for no unchecked member variables.">, Documentation; +def UnretainedLambdaCapturesChecker : Checker<"UnretainedLambdaCapturesChecker">, + HelpText<"Check unretained lambda captures.">, + Documentation; + def UncountedCallArgsChecker : Checker<"UncountedCallArgsChecker">, HelpText<"Check uncounted call arguments.">, Documentation; diff --git a/clang/lib/StaticAnalyzer/Checkers/CMakeLists.txt b/clang/lib/StaticAnalyzer/Checkers/CMakeLists.txt index f1a24dc2b8f62..d19623b3f2480 100644 --- a/clang/lib/StaticAnalyzer/Checkers/CMakeLists.txt +++ b/clang/lib/StaticAnalyzer/Checkers/CMakeLists.txt @@ -133,14 +133,14 @@ add_clang_library(clangStaticAnalyzerCheckers VLASizeChecker.cpp ValistChecker.cpp VirtualCallChecker.cpp - WebKit/RawPtrRefMemberChecker.cpp WebKit/ASTUtils.cpp WebKit/MemoryUnsafeCastChecker.cpp WebKit/PtrTypesSemantics.cpp WebKit/RefCntblBaseVirtualDtorChecker.cpp WebKit/RawPtrRefCallArgsChecker.cpp - WebKit/UncountedLambdaCapturesChecker.cpp + WebKit/RawPtrRefLambdaCapturesChecker.cpp WebKit/RawPtrRefLocalVarsChecker.cpp + WebKit/RawPtrRefMemberChecker.cpp LINK_LIBS clangAST diff --git a/clang/lib/StaticAnalyzer/Checkers/WebKit/UncountedLambdaCapturesChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/WebKit/RawPtrRefLambdaCapturesChecker.cpp similarity index 76% rename from clang/lib/StaticAnalyzer/Checkers/WebKit/UncountedLambdaCapturesChecker.cpp rename to clang/lib/StaticAnalyzer/Checkers/WebKit/RawPtrRefLambdaCapturesChecker.cpp index c9aaf5d68ed81..a45a0ed89d7dc 100644 --- a/clang/lib/StaticAnalyzer/Checkers/WebKit/UncountedLambdaCapturesChecker.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/WebKit/RawPtrRefLambdaCapturesChecker.cpp @@ -21,15 +21,23 @@ using namespace clang; using namespace ento; namespace { -class UncountedLambdaCapturesChecker +class RawPtrRefLambdaCapturesChecker : public Checker> { private: - BugType Bug{this, "Lambda capture of uncounted variable", - "WebKit coding guidelines"}; + BugType Bug; mutable BugReporter *BR = nullptr; TrivialFunctionAnalysis TFA; +protected: + mutable std::optional RTC; + public: + RawPtrRefLambdaCapturesChecker(const char *description) + : Bug(this, description, "WebKit coding guidelines") {} + + virtual std::optional isUnsafePtr(QualType) const = 0; + virtual const char *ptrKind(QualType QT) const = 0; + void checkASTDecl(const TranslationUnitDecl *TUD, AnalysisManager &MGR, BugReporter &BRArg) const { BR = &BRArg; @@ -38,7 +46,7 @@ class UncountedLambdaCapturesChecker // visit template instantiations or lambda classes. We // want to visit those, so we make our own RecursiveASTVisitor. struct LocalVisitor : public RecursiveASTVisitor { - const UncountedLambdaCapturesChecker *Checker; + const RawPtrRefLambdaCapturesChecker *Checker; llvm::DenseSet DeclRefExprsToIgnore; llvm::DenseSet LambdasToIgnore; llvm::DenseSet ProtectedThisDecls; @@ -48,7 +56,7 @@ class UncountedLambdaCapturesChecker using Base = RecursiveASTVisitor; - explicit LocalVisitor(const UncountedLambdaCapturesChecker *Checker) + explicit LocalVisitor(const RawPtrRefLambdaCapturesChecker *Checker) : Checker(Checker) { assert(Checker); } @@ -63,16 +71,23 @@ class UncountedLambdaCapturesChecker return Base::TraverseCXXMethodDecl(CXXMD); } + bool VisitTypedefDecl(TypedefDecl *TD) { + if (Checker->RTC) + Checker->RTC->visitTypedef(TD); + return true; + } + bool shouldCheckThis() { auto result = - !ClsType.isNull() ? isUnsafePtr(ClsType, false) : std::nullopt; + !ClsType.isNull() ? Checker->isUnsafePtr(ClsType) : std::nullopt; return result && *result; } bool VisitLambdaExpr(LambdaExpr *L) { if (LambdasToIgnore.contains(L)) return true; - Checker->visitLambdaExpr(L, shouldCheckThis() && !hasProtectedThis(L)); + Checker->visitLambdaExpr(L, shouldCheckThis() && !hasProtectedThis(L), + ClsType); return true; } @@ -100,7 +115,8 @@ class UncountedLambdaCapturesChecker if (!L) return true; LambdasToIgnore.insert(L); - Checker->visitLambdaExpr(L, shouldCheckThis() && !hasProtectedThis(L)); + Checker->visitLambdaExpr(L, shouldCheckThis() && !hasProtectedThis(L), + ClsType); return true; } @@ -125,8 +141,8 @@ class UncountedLambdaCapturesChecker if (auto *L = findLambdaInArg(Arg)) { LambdasToIgnore.insert(L); if (!Param->hasAttr()) - Checker->visitLambdaExpr(L, shouldCheckThis() && - !hasProtectedThis(L)); + Checker->visitLambdaExpr( + L, shouldCheckThis() && !hasProtectedThis(L), ClsType); } ++ArgIndex; } @@ -146,8 +162,8 @@ class UncountedLambdaCapturesChecker if (auto *L = findLambdaInArg(Arg)) { LambdasToIgnore.insert(L); if (!Param->hasAttr() && !TreatAllArgsAsNoEscape) - Checker->visitLambdaExpr(L, shouldCheckThis() && - !hasProtectedThis(L)); + Checker->visitLambdaExpr( + L, shouldCheckThis() && !hasProtectedThis(L), ClsType); } ++ArgIndex; } @@ -172,14 +188,22 @@ class UncountedLambdaCapturesChecker auto *CtorArg = CE->getArg(0)->IgnoreParenCasts(); if (!CtorArg) return nullptr; - if (auto *Lambda = dyn_cast(CtorArg)) { + auto *InnerCE = dyn_cast_or_null(CtorArg); + if (InnerCE && InnerCE->getNumArgs()) + CtorArg = InnerCE->getArg(0)->IgnoreParenCasts(); + auto updateIgnoreList = [&] { ConstructToIgnore.insert(CE); + if (InnerCE) + ConstructToIgnore.insert(InnerCE); + }; + if (auto *Lambda = dyn_cast(CtorArg)) { + updateIgnoreList(); return Lambda; } if (auto *TempExpr = dyn_cast(CtorArg)) { E = TempExpr->getSubExpr()->IgnoreParenCasts(); if (auto *Lambda = dyn_cast(E)) { - ConstructToIgnore.insert(CE); + updateIgnoreList(); return Lambda; } } @@ -192,10 +216,14 @@ class UncountedLambdaCapturesChecker auto *Init = VD->getInit(); if (!Init) return nullptr; + if (auto *Lambda = dyn_cast(Init)) { + updateIgnoreList(); + return Lambda; + } TempExpr = dyn_cast(Init->IgnoreParenCasts()); if (!TempExpr) return nullptr; - ConstructToIgnore.insert(CE); + updateIgnoreList(); return dyn_cast_or_null(TempExpr->getSubExpr()); } @@ -229,7 +257,7 @@ class UncountedLambdaCapturesChecker DeclRefExprsToIgnore.insert(ArgRef); LambdasToIgnore.insert(L); Checker->visitLambdaExpr(L, shouldCheckThis() && !hasProtectedThis(L), - /* ignoreParamVarDecl */ true); + ClsType, /* ignoreParamVarDecl */ true); } bool hasProtectedThis(LambdaExpr *L) { @@ -296,10 +324,12 @@ class UncountedLambdaCapturesChecker }; LocalVisitor visitor(this); + if (RTC) + RTC->visitTranslationUnitDecl(TUD); visitor.TraverseDecl(const_cast(TUD)); } - void visitLambdaExpr(LambdaExpr *L, bool shouldCheckThis, + void visitLambdaExpr(LambdaExpr *L, bool shouldCheckThis, const QualType T, bool ignoreParamVarDecl = false) const { if (TFA.isTrivial(L->getBody())) return; @@ -309,13 +339,13 @@ class UncountedLambdaCapturesChecker if (ignoreParamVarDecl && isa(CapturedVar)) continue; QualType CapturedVarQualType = CapturedVar->getType(); - auto IsUncountedPtr = isUnsafePtr(CapturedVar->getType(), false); + auto IsUncountedPtr = isUnsafePtr(CapturedVar->getType()); if (IsUncountedPtr && *IsUncountedPtr) reportBug(C, CapturedVar, CapturedVarQualType); } else if (C.capturesThis() && shouldCheckThis) { if (ignoreParamVarDecl) // this is always a parameter to this function. continue; - reportBugOnThisPtr(C); + reportBugOnThisPtr(C, T); } } } @@ -324,6 +354,9 @@ class UncountedLambdaCapturesChecker const QualType T) const { assert(CapturedVar); + if (isa(CapturedVar) && !Capture.getLocation().isValid()) + return; // Ignore implicit captruing of self. + SmallString<100> Buf; llvm::raw_svector_ostream Os(Buf); @@ -332,22 +365,22 @@ class UncountedLambdaCapturesChecker } else { Os << "Implicitly captured "; } - if (T->isPointerType()) { + if (isa(T) || isa(T)) { Os << "raw-pointer "; } else { - assert(T->isReferenceType()); Os << "reference "; } - printQuotedQualifiedName(Os, Capture.getCapturedVar()); - Os << " to ref-counted type or CheckedPtr-capable type is unsafe."; + printQuotedQualifiedName(Os, CapturedVar); + Os << " to " << ptrKind(T) << " type is unsafe."; PathDiagnosticLocation BSLoc(Capture.getLocation(), BR->getSourceManager()); auto Report = std::make_unique(Bug, Os.str(), BSLoc); BR->emitReport(std::move(Report)); } - void reportBugOnThisPtr(const LambdaCapture &Capture) const { + void reportBugOnThisPtr(const LambdaCapture &Capture, + const QualType T) const { SmallString<100> Buf; llvm::raw_svector_ostream Os(Buf); @@ -357,14 +390,54 @@ class UncountedLambdaCapturesChecker Os << "Implicitly captured "; } - Os << "raw-pointer 'this' to ref-counted type or CheckedPtr-capable type " - "is unsafe."; + Os << "raw-pointer 'this' to " << ptrKind(T) << " type is unsafe."; PathDiagnosticLocation BSLoc(Capture.getLocation(), BR->getSourceManager()); auto Report = std::make_unique(Bug, Os.str(), BSLoc); BR->emitReport(std::move(Report)); } }; + +class UncountedLambdaCapturesChecker : public RawPtrRefLambdaCapturesChecker { +public: + UncountedLambdaCapturesChecker() + : RawPtrRefLambdaCapturesChecker("Lambda capture of uncounted or " + "unchecked variable") {} + + std::optional isUnsafePtr(QualType QT) const final { + auto result1 = isUncountedPtr(QT); + auto result2 = isUncheckedPtr(QT); + if (result1 && *result1) + return true; + if (result2 && *result2) + return true; + if (result1) + return *result1; + return result2; + } + + const char *ptrKind(QualType QT) const final { + if (isUncounted(QT)) + return "uncounted"; + return "unchecked"; + } +}; + +class UnretainedLambdaCapturesChecker : public RawPtrRefLambdaCapturesChecker { +public: + UnretainedLambdaCapturesChecker() + : RawPtrRefLambdaCapturesChecker("Lambda capture of unretained " + "variables") { + RTC = RetainTypeChecker(); + } + + std::optional isUnsafePtr(QualType QT) const final { + return RTC->isUnretained(QT); + } + + const char *ptrKind(QualType QT) const final { return "unretained"; } +}; + } // namespace void ento::registerUncountedLambdaCapturesChecker(CheckerManager &Mgr) { @@ -375,3 +448,12 @@ bool ento::shouldRegisterUncountedLambdaCapturesChecker( const CheckerManager &mgr) { return true; } + +void ento::registerUnretainedLambdaCapturesChecker(CheckerManager &Mgr) { + Mgr.registerChecker(); +} + +bool ento::shouldRegisterUnretainedLambdaCapturesChecker( + const CheckerManager &mgr) { + return true; +} diff --git a/clang/test/Analysis/Checkers/WebKit/objc-mock-types.h b/clang/test/Analysis/Checkers/WebKit/objc-mock-types.h index 7bb33bcb6cf44..9b13810d0c5c9 100644 --- a/clang/test/Analysis/Checkers/WebKit/objc-mock-types.h +++ b/clang/test/Analysis/Checkers/WebKit/objc-mock-types.h @@ -1,30 +1,94 @@ @class NSString; @class NSArray; @class NSMutableArray; +#define nil ((id)0) #define CF_BRIDGED_TYPE(T) __attribute__((objc_bridge(T))) +#define CF_BRIDGED_MUTABLE_TYPE(T) __attribute__((objc_bridge_mutable(T))) typedef CF_BRIDGED_TYPE(id) void * CFTypeRef; +typedef signed char BOOL; typedef signed long CFIndex; -typedef const struct __CFAllocator * CFAllocatorRef; +typedef unsigned long NSUInteger; +typedef const struct CF_BRIDGED_TYPE(id) __CFAllocator * CFAllocatorRef; typedef const struct CF_BRIDGED_TYPE(NSString) __CFString * CFStringRef; typedef const struct CF_BRIDGED_TYPE(NSArray) __CFArray * CFArrayRef; -typedef struct CF_BRIDGED_TYPE(NSMutableArray) __CFArray * CFMutableArrayRef; +typedef struct CF_BRIDGED_MUTABLE_TYPE(NSMutableArray) __CFArray * CFMutableArrayRef; +typedef struct CF_BRIDGED_MUTABLE_TYPE(CFRunLoopRef) __CFRunLoop * CFRunLoopRef; extern const CFAllocatorRef kCFAllocatorDefault; +typedef struct _NSZone NSZone; CFMutableArrayRef CFArrayCreateMutable(CFAllocatorRef allocator, CFIndex capacity); extern void CFArrayAppendValue(CFMutableArrayRef theArray, const void *value); CFArrayRef CFArrayCreate(CFAllocatorRef allocator, const void **values, CFIndex numValues); CFIndex CFArrayGetCount(CFArrayRef theArray); +CFRunLoopRef CFRunLoopGetCurrent(void); +CFRunLoopRef CFRunLoopGetMain(void); extern CFTypeRef CFRetain(CFTypeRef cf); extern void CFRelease(CFTypeRef cf); +#define CFSTR(cStr) ((CFStringRef) __builtin___CFStringMakeConstantString ("" cStr "")) +extern Class NSClassFromString(NSString *aClassName); __attribute__((objc_root_class)) @interface NSObject + (instancetype) alloc; ++ (Class) class; ++ (Class) superclass; - (instancetype) init; - (instancetype)retain; - (void)release; +- (BOOL)isKindOfClass:(Class)aClass; +@end + +@protocol NSCopying +- (id)copyWithZone:(NSZone *)zone; +@end + +@protocol NSFastEnumeration +- (int)countByEnumeratingWithState:(void *)state objects:(id *)objects count:(unsigned)count; +- (void)protocolMethod; +@end + +@interface NSEnumerator +@end + +@interface NSDictionary : NSObject +- (NSUInteger)count; +- (id)objectForKey:(id)aKey; +- (id)objectForKeyedSubscript:(id)aKey; +- (NSEnumerator *)keyEnumerator; ++ (id)dictionary; ++ (id)dictionaryWithObject:(id)object forKey:(id )key; ++ (instancetype)dictionaryWithObjects:(const id [])objects forKeys:(const id [])keys count:(NSUInteger)cnt; +@end + +@interface NSArray : NSObject +- (NSUInteger)count; +- (NSEnumerator *)objectEnumerator; ++ (NSArray *)arrayWithObjects:(const id [])objects count:(NSUInteger)count; +@end + +@interface NSString : NSObject +- (NSUInteger)length; +- (NSString *)stringByAppendingString:(NSString *)aString; +- ( const char *)UTF8String; +- (id)initWithUTF8String:(const char *)nullTerminatedCString; ++ (id)stringWithUTF8String:(const char *)nullTerminatedCString; +@end + +@interface NSMutableString : NSString +@end + +@interface NSValue : NSObject +- (void)getValue:(void *)value; +@end + +@interface NSNumber : NSValue +- (char)charValue; +- (id)initWithInt:(int)value; ++ (NSNumber *)numberWithInt:(int)value; @end @interface SomeObj : NSObject +- (SomeObj *)mutableCopy; +- (SomeObj *)copyWithValue:(int)value; - (void)doWork; - (SomeObj *)other; - (SomeObj *)next; @@ -57,28 +121,34 @@ template struct RetainPtr { PtrType t; RetainPtr() : t(nullptr) { } - RetainPtr(PtrType t) : t(t) { if (t) - CFRetain(t); + CFRetain(toCFTypeRef(t)); } - RetainPtr(RetainPtr&& o) - : RetainPtr(o.t) - { - o.t = nullptr; - } - RetainPtr(const RetainPtr& o) + RetainPtr(RetainPtr&&); + RetainPtr(const RetainPtr&); + template + RetainPtr(const RetainPtr& o) : RetainPtr(o.t) + {} + RetainPtr operator=(const RetainPtr& o) { + if (t) + CFRelease(toCFTypeRef(t)); + t = o.t; + if (t) + CFRetain(toCFTypeRef(t)); + return *this; } - RetainPtr operator=(const RetainPtr& o) + template + RetainPtr operator=(const RetainPtr& o) { if (t) - CFRelease(t); + CFRelease(toCFTypeRef(t)); t = o.t; if (t) - CFRetain(t); + CFRetain(toCFTypeRef(t)); return *this; } ~RetainPtr() { @@ -86,7 +156,7 @@ template struct RetainPtr { } void clear() { if (t) - CFRelease(t); + CFRelease(toCFTypeRef(t)); t = nullptr; } void swap(RetainPtr& o) { @@ -102,10 +172,19 @@ template struct RetainPtr { swap(o); return *this; } + PtrType leakRef() + { + PtrType s = t; + t = nullptr; + return s; + } operator PtrType() const { return t; } operator bool() const { return t; } private: + CFTypeRef toCFTypeRef(id ptr) { return (__bridge CFTypeRef)ptr; } + CFTypeRef toCFTypeRef(const void* ptr) { return (CFTypeRef)ptr; } + template friend RetainPtr adoptNS(U*); template friend RetainPtr adoptCF(U); @@ -113,9 +192,26 @@ template struct RetainPtr { RetainPtr(PtrType t, AdoptTag) : t(t) { } }; +template +RetainPtr::RetainPtr(RetainPtr&& o) + : RetainPtr(o.t) +{ + o.t = nullptr; +} + +template +RetainPtr::RetainPtr(const RetainPtr& o) + : RetainPtr(o.t) +{ +} + template RetainPtr adoptNS(T* t) { +#if __has_feature(objc_arc) + return t; +#else return RetainPtr(t, RetainPtr::Adopt); +#endif } template @@ -123,9 +219,31 @@ RetainPtr adoptCF(T t) { return RetainPtr(t, RetainPtr::Adopt); } +template inline RetainPtr retainPtr(T ptr) +{ + return ptr; +} + +template inline RetainPtr retainPtr(T* ptr) +{ + return ptr; +} + +inline NSObject *bridge_cast(CFTypeRef object) +{ + return (__bridge NSObject *)object; +} + +inline CFTypeRef bridge_cast(NSObject *object) +{ + return (__bridge CFTypeRef)object; +} + } using WTF::RetainPtr; using WTF::adoptNS; using WTF::adoptCF; +using WTF::retainPtr; using WTF::downcast; +using WTF::bridge_cast; \ No newline at end of file diff --git a/clang/test/Analysis/Checkers/WebKit/uncounted-lambda-captures-decl-protects-this-crash.cpp b/clang/test/Analysis/Checkers/WebKit/uncounted-lambda-captures-decl-protects-this-crash.cpp index 840433db5133a..0c10c69a97eda 100644 --- a/clang/test/Analysis/Checkers/WebKit/uncounted-lambda-captures-decl-protects-this-crash.cpp +++ b/clang/test/Analysis/Checkers/WebKit/uncounted-lambda-captures-decl-protects-this-crash.cpp @@ -28,9 +28,9 @@ struct Obj { void foo(Foo foo) { bar([this](auto baz) { - // expected-warning@-1{{Captured raw-pointer 'this' to ref-counted type or CheckedPtr-capable type is unsafe [webkit.UncountedLambdaCapturesChecker]}} + // expected-warning@-1{{Captured raw-pointer 'this' to uncounted type is unsafe [webkit.UncountedLambdaCapturesChecker]}} bar([this, foo = *baz, foo2 = !baz](auto&&) { - // expected-warning@-1{{Captured raw-pointer 'this' to ref-counted type or CheckedPtr-capable type is unsafe [webkit.UncountedLambdaCapturesChecker]}} + // expected-warning@-1{{Captured raw-pointer 'this' to uncounted type is unsafe [webkit.UncountedLambdaCapturesChecker]}} someFunction(); }); }); diff --git a/clang/test/Analysis/Checkers/WebKit/uncounted-lambda-captures.cpp b/clang/test/Analysis/Checkers/WebKit/uncounted-lambda-captures.cpp index 82058bf13d137..1746d2b93d469 100644 --- a/clang/test/Analysis/Checkers/WebKit/uncounted-lambda-captures.cpp +++ b/clang/test/Analysis/Checkers/WebKit/uncounted-lambda-captures.cpp @@ -94,22 +94,22 @@ void callAsync(const WTF::Function&); void raw_ptr() { RefCountable* ref_countable = make_obj(); auto foo1 = [ref_countable](){ - // expected-warning@-1{{Captured raw-pointer 'ref_countable' to ref-counted type or CheckedPtr-capable type is unsafe [webkit.UncountedLambdaCapturesChecker]}} + // expected-warning@-1{{Captured raw-pointer 'ref_countable' to uncounted type is unsafe [webkit.UncountedLambdaCapturesChecker]}} ref_countable->method(); }; auto foo2 = [&ref_countable](){ - // expected-warning@-1{{Captured raw-pointer 'ref_countable' to ref-counted type or CheckedPtr-capable type is unsafe [webkit.UncountedLambdaCapturesChecker]}} + // expected-warning@-1{{Captured raw-pointer 'ref_countable' to uncounted type is unsafe [webkit.UncountedLambdaCapturesChecker]}} ref_countable->method(); }; auto foo3 = [&](){ ref_countable->method(); - // expected-warning@-1{{Implicitly captured raw-pointer 'ref_countable' to ref-counted type or CheckedPtr-capable type is unsafe [webkit.UncountedLambdaCapturesChecker]}} + // expected-warning@-1{{Implicitly captured raw-pointer 'ref_countable' to uncounted type is unsafe [webkit.UncountedLambdaCapturesChecker]}} ref_countable = nullptr; }; auto foo4 = [=](){ ref_countable->method(); - // expected-warning@-1{{Implicitly captured raw-pointer 'ref_countable' to ref-counted type or CheckedPtr-capable type is unsafe [webkit.UncountedLambdaCapturesChecker]}} + // expected-warning@-1{{Implicitly captured raw-pointer 'ref_countable' to uncounted type is unsafe [webkit.UncountedLambdaCapturesChecker]}} }; call(foo1); @@ -128,13 +128,13 @@ void references() { RefCountable automatic; RefCountable& ref_countable_ref = automatic; auto foo1 = [ref_countable_ref](){ ref_countable_ref.constMethod(); }; - // expected-warning@-1{{Captured reference 'ref_countable_ref' to ref-counted type or CheckedPtr-capable type is unsafe [webkit.UncountedLambdaCapturesChecker]}} + // expected-warning@-1{{Captured reference 'ref_countable_ref' to uncounted type is unsafe [webkit.UncountedLambdaCapturesChecker]}} auto foo2 = [&ref_countable_ref](){ ref_countable_ref.method(); }; - // expected-warning@-1{{Captured reference 'ref_countable_ref' to ref-counted type or CheckedPtr-capable type is unsafe [webkit.UncountedLambdaCapturesChecker]}} + // expected-warning@-1{{Captured reference 'ref_countable_ref' to uncounted type is unsafe [webkit.UncountedLambdaCapturesChecker]}} auto foo3 = [&](){ ref_countable_ref.method(); }; - // expected-warning@-1{{Implicitly captured reference 'ref_countable_ref' to ref-counted type or CheckedPtr-capable type is unsafe [webkit.UncountedLambdaCapturesChecker]}} + // expected-warning@-1{{Implicitly captured reference 'ref_countable_ref' to uncounted type is unsafe [webkit.UncountedLambdaCapturesChecker]}} auto foo4 = [=](){ ref_countable_ref.constMethod(); }; - // expected-warning@-1{{Implicitly captured reference 'ref_countable_ref' to ref-counted type or CheckedPtr-capable type is unsafe [webkit.UncountedLambdaCapturesChecker]}} + // expected-warning@-1{{Implicitly captured reference 'ref_countable_ref' to uncounted type is unsafe [webkit.UncountedLambdaCapturesChecker]}} call(foo1); call(foo2); @@ -187,7 +187,7 @@ void noescape_lambda() { otherObj->method(); }, [&](RefCountable& obj) { otherObj->method(); - // expected-warning@-1{{Implicitly captured raw-pointer 'otherObj' to ref-counted type or CheckedPtr-capable type is unsafe [webkit.UncountedLambdaCapturesChecker]}} + // expected-warning@-1{{Implicitly captured raw-pointer 'otherObj' to uncounted type is unsafe [webkit.UncountedLambdaCapturesChecker]}} }); ([&] { someObj->method(); @@ -217,7 +217,7 @@ struct RefCountableWithLambdaCapturingThis { void method_captures_this_unsafe() { auto lambda = [&]() { nonTrivial(); - // expected-warning@-1{{Implicitly captured raw-pointer 'this' to ref-counted type or CheckedPtr-capable type is unsafe [webkit.UncountedLambdaCapturesChecker]}} + // expected-warning@-1{{Implicitly captured raw-pointer 'this' to uncounted type is unsafe [webkit.UncountedLambdaCapturesChecker]}} }; call(lambda); } @@ -225,7 +225,7 @@ struct RefCountableWithLambdaCapturingThis { void method_captures_this_unsafe_capture_local_var_explicitly() { RefCountable* x = make_obj(); call([this, protectedThis = RefPtr { this }, x]() { - // expected-warning@-1{{Captured raw-pointer 'x' to ref-counted type or CheckedPtr-capable type is unsafe [webkit.UncountedLambdaCapturesChecker]}} + // expected-warning@-1{{Captured raw-pointer 'x' to uncounted type is unsafe [webkit.UncountedLambdaCapturesChecker]}} nonTrivial(); x->method(); }); @@ -234,7 +234,7 @@ struct RefCountableWithLambdaCapturingThis { void method_captures_this_with_other_protected_var() { RefCountable* x = make_obj(); call([this, protectedX = RefPtr { x }]() { - // expected-warning@-1{{Captured raw-pointer 'this' to ref-counted type or CheckedPtr-capable type is unsafe [webkit.UncountedLambdaCapturesChecker]}} + // expected-warning@-1{{Captured raw-pointer 'this' to uncounted type is unsafe [webkit.UncountedLambdaCapturesChecker]}} nonTrivial(); protectedX->method(); }); @@ -243,7 +243,7 @@ struct RefCountableWithLambdaCapturingThis { void method_captures_this_unsafe_capture_local_var_explicitly_with_deref() { RefCountable* x = make_obj(); call([this, protectedThis = Ref { *this }, x]() { - // expected-warning@-1{{Captured raw-pointer 'x' to ref-counted type or CheckedPtr-capable type is unsafe [webkit.UncountedLambdaCapturesChecker]}} + // expected-warning@-1{{Captured raw-pointer 'x' to uncounted type is unsafe [webkit.UncountedLambdaCapturesChecker]}} nonTrivial(); x->method(); }); @@ -252,7 +252,7 @@ struct RefCountableWithLambdaCapturingThis { void method_captures_this_unsafe_local_var_via_vardecl() { RefCountable* x = make_obj(); auto lambda = [this, protectedThis = Ref { *this }, x]() { - // expected-warning@-1{{Captured raw-pointer 'x' to ref-counted type or CheckedPtr-capable type is unsafe [webkit.UncountedLambdaCapturesChecker]}} + // expected-warning@-1{{Captured raw-pointer 'x' to uncounted type is unsafe [webkit.UncountedLambdaCapturesChecker]}} nonTrivial(); x->method(); }; @@ -330,7 +330,7 @@ struct RefCountableWithLambdaCapturingThis { void method_nested_lambda3() { callAsync([this, protectedThis = RefPtr { this }] { callAsync([this] { - // expected-warning@-1{{Captured raw-pointer 'this' to ref-counted type or CheckedPtr-capable type is unsafe [webkit.UncountedLambdaCapturesChecker]}} + // expected-warning@-1{{Captured raw-pointer 'this' to uncounted type is unsafe [webkit.UncountedLambdaCapturesChecker]}} nonTrivial(); }); }); @@ -380,10 +380,10 @@ void lambda_converted_to_function(RefCountable* obj) { callFunction([&]() { obj->method(); - // expected-warning@-1{{Implicitly captured raw-pointer 'obj' to ref-counted type or CheckedPtr-capable type is unsafe [webkit.UncountedLambdaCapturesChecker]}} + // expected-warning@-1{{Implicitly captured raw-pointer 'obj' to uncounted type is unsafe [webkit.UncountedLambdaCapturesChecker]}} }); callFunctionOpaque([&]() { obj->method(); - // expected-warning@-1{{Implicitly captured raw-pointer 'obj' to ref-counted type or CheckedPtr-capable type is unsafe [webkit.UncountedLambdaCapturesChecker]}} + // expected-warning@-1{{Implicitly captured raw-pointer 'obj' to uncounted type is unsafe [webkit.UncountedLambdaCapturesChecker]}} }); } diff --git a/clang/test/Analysis/Checkers/WebKit/unretained-lambda-captures-arc.mm b/clang/test/Analysis/Checkers/WebKit/unretained-lambda-captures-arc.mm new file mode 100644 index 0000000000000..4e3d9c2708d96 --- /dev/null +++ b/clang/test/Analysis/Checkers/WebKit/unretained-lambda-captures-arc.mm @@ -0,0 +1,273 @@ +// RUN: %clang_analyze_cc1 -analyzer-checker=alpha.webkit.UnretainedLambdaCapturesChecker -fobjc-arc -verify %s + +#include "objc-mock-types.h" + +namespace std { + +template +class unique_ptr { +private: + T *t; + +public: + unique_ptr() : t(nullptr) { } + unique_ptr(T *t) : t(t) { } + ~unique_ptr() { + if (t) + delete t; + } + template unique_ptr(unique_ptr&& u) + : t(u.t) + { + u.t = nullptr; + } + T *get() const { return t; } + T *operator->() const { return t; } + T &operator*() const { return *t; } + unique_ptr &operator=(T *) { return *this; } + explicit operator bool() const { return !!t; } +}; + +}; + +namespace WTF { + +namespace Detail { + +template +class CallableWrapperBase { +public: + virtual ~CallableWrapperBase() { } + virtual Out call(In...) = 0; +}; + +template class CallableWrapper; + +template +class CallableWrapper : public CallableWrapperBase { +public: + explicit CallableWrapper(CallableType& callable) + : m_callable(callable) { } + Out call(In... in) final { return m_callable(in...); } + +private: + CallableType m_callable; +}; + +} // namespace Detail + +template class Function; + +template Function adopt(Detail::CallableWrapperBase*); + +template +class Function { +public: + using Impl = Detail::CallableWrapperBase; + + Function() = default; + + template + Function(FunctionType f) + : m_callableWrapper(new Detail::CallableWrapper(f)) { } + + Out operator()(In... in) const { return m_callableWrapper->call(in...); } + explicit operator bool() const { return !!m_callableWrapper; } + +private: + enum AdoptTag { Adopt }; + Function(Impl* impl, AdoptTag) + : m_callableWrapper(impl) + { + } + + friend Function adopt(Impl*); + + std::unique_ptr m_callableWrapper; +}; + +template Function adopt(Detail::CallableWrapperBase* impl) +{ + return Function(impl, Function::Adopt); +} + +template +class HashMap { +public: + HashMap(); + HashMap([[clang::noescape]] const Function&); + void ensure(const KeyType&, [[clang::noescape]] const Function&); + bool operator+([[clang::noescape]] const Function&) const; + static void ifAny(HashMap, [[clang::noescape]] const Function&); + +private: + ValueType* m_table { nullptr }; +}; + +} // namespace WTF + +struct A { + static void b(); +}; + +SomeObj* make_obj(); +CFMutableArrayRef make_cf(); + +void someFunction(); +template void call(Callback callback) { + someFunction(); + callback(); +} +void callAsync(const WTF::Function&); + +void raw_ptr() { + SomeObj* obj = make_obj(); + auto foo1 = [obj](){ + [obj doWork]; + }; + call(foo1); + + auto foo2 = [&obj](){ + [obj doWork]; + }; + auto foo3 = [&](){ + [obj doWork]; + obj = nullptr; + }; + auto foo4 = [=](){ + [obj doWork]; + }; + + auto cf = make_cf(); + auto bar1 = [cf](){ + // expected-warning@-1{{Captured reference 'cf' to unretained type is unsafe [alpha.webkit.UnretainedLambdaCapturesChecker]}} + CFArrayAppendValue(cf, nullptr); + }; + auto bar2 = [&cf](){ + // expected-warning@-1{{Captured reference 'cf' to unretained type is unsafe [alpha.webkit.UnretainedLambdaCapturesChecker]}} + CFArrayAppendValue(cf, nullptr); + }; + auto bar3 = [&](){ + CFArrayAppendValue(cf, nullptr); + // expected-warning@-1{{Implicitly captured reference 'cf' to unretained type is unsafe [alpha.webkit.UnretainedLambdaCapturesChecker]}} + cf = nullptr; + }; + auto bar4 = [=](){ + CFArrayAppendValue(cf, nullptr); + // expected-warning@-1{{Implicitly captured reference 'cf' to unretained type is unsafe [alpha.webkit.UnretainedLambdaCapturesChecker]}} + }; + + call(foo1); + call(foo2); + call(foo3); + call(foo4); + + call(bar1); + call(bar2); + call(bar3); + call(bar4); +} + +void quiet() { +// This code is not expected to trigger any warnings. + SomeObj *obj; + + auto foo3 = [&]() {}; + auto foo4 = [=]() {}; + + call(foo3); + call(foo4); + + obj = nullptr; +} + +template +void map(SomeObj* start, [[clang::noescape]] Callback&& callback) +{ + while (start) { + callback(start); + start = [start next]; + } +} + +template +void doubleMap(SomeObj* start, [[clang::noescape]] Callback1&& callback1, Callback2&& callback2) +{ + while (start) { + callback1(start); + callback2(start); + start = [start next]; + } +} + +template +void get_count_cf(CFArrayRef array, [[clang::noescape]] Callback1&& callback1, Callback2&& callback2) +{ + auto count = CFArrayGetCount(array); + callback1(count); + callback2(count); +} + +void noescape_lambda() { + SomeObj* someObj = make_obj(); + SomeObj* otherObj = make_obj(); + map(make_obj(), [&](SomeObj *obj) { + [otherObj doWork]; + }); + doubleMap(make_obj(), [&](SomeObj *obj) { + [otherObj doWork]; + }, [&](SomeObj *obj) { + [otherObj doWork]; + }); + ([&] { + [someObj doWork]; + })(); + + CFMutableArrayRef someCF = make_cf(); + get_count_cf(make_cf(), [&](CFIndex count) { + CFArrayAppendValue(someCF, nullptr); + }, [&](CFIndex count) { + CFArrayAppendValue(someCF, nullptr); + // expected-warning@-1{{Implicitly captured reference 'someCF' to unretained type is unsafe [alpha.webkit.UnretainedLambdaCapturesChecker]}} + }); +} + +void callFunctionOpaque(WTF::Function&&); +void callFunction(WTF::Function&& function) { + someFunction(); + function(); +} + +void lambda_converted_to_function(SomeObj* obj, CFMutableArrayRef cf) +{ + callFunction([&]() { + [obj doWork]; + CFArrayAppendValue(cf, nullptr); + // expected-warning@-1{{Implicitly captured reference 'cf' to unretained type is unsafe [alpha.webkit.UnretainedLambdaCapturesChecker]}} + }); + callFunctionOpaque([&]() { + [obj doWork]; + CFArrayAppendValue(cf, nullptr); + // expected-warning@-1{{Implicitly captured reference 'cf' to unretained type is unsafe [alpha.webkit.UnretainedLambdaCapturesChecker]}} + }); +} + +@interface ObjWithSelf : NSObject { + RetainPtr delegate; +} +-(void)doWork; +-(void)run; +@end + +@implementation ObjWithSelf +-(void)doWork { + auto doWork = [&] { + someFunction(); + [delegate doWork]; + }; + callFunctionOpaque(doWork); +} +-(void)run { + someFunction(); +} +@end \ No newline at end of file diff --git a/clang/test/Analysis/Checkers/WebKit/unretained-lambda-captures.mm b/clang/test/Analysis/Checkers/WebKit/unretained-lambda-captures.mm new file mode 100644 index 0000000000000..073eff9386baa --- /dev/null +++ b/clang/test/Analysis/Checkers/WebKit/unretained-lambda-captures.mm @@ -0,0 +1,296 @@ +// RUN: %clang_analyze_cc1 -analyzer-checker=alpha.webkit.UnretainedLambdaCapturesChecker -verify %s + +#include "objc-mock-types.h" + +namespace std { + +template +class unique_ptr { +private: + T *t; + +public: + unique_ptr() : t(nullptr) { } + unique_ptr(T *t) : t(t) { } + ~unique_ptr() { + if (t) + delete t; + } + template unique_ptr(unique_ptr&& u) + : t(u.t) + { + u.t = nullptr; + } + T *get() const { return t; } + T *operator->() const { return t; } + T &operator*() const { return *t; } + unique_ptr &operator=(T *) { return *this; } + explicit operator bool() const { return !!t; } +}; + +}; + +namespace WTF { + +namespace Detail { + +template +class CallableWrapperBase { +public: + virtual ~CallableWrapperBase() { } + virtual Out call(In...) = 0; +}; + +template class CallableWrapper; + +template +class CallableWrapper : public CallableWrapperBase { +public: + explicit CallableWrapper(CallableType& callable) + : m_callable(callable) { } + Out call(In... in) final { return m_callable(in...); } + +private: + CallableType m_callable; +}; + +} // namespace Detail + +template class Function; + +template Function adopt(Detail::CallableWrapperBase*); + +template +class Function { +public: + using Impl = Detail::CallableWrapperBase; + + Function() = default; + + template + Function(FunctionType f) + : m_callableWrapper(new Detail::CallableWrapper(f)) { } + + Out operator()(In... in) const { return m_callableWrapper->call(in...); } + explicit operator bool() const { return !!m_callableWrapper; } + +private: + enum AdoptTag { Adopt }; + Function(Impl* impl, AdoptTag) + : m_callableWrapper(impl) + { + } + + friend Function adopt(Impl*); + + std::unique_ptr m_callableWrapper; +}; + +template Function adopt(Detail::CallableWrapperBase* impl) +{ + return Function(impl, Function::Adopt); +} + +template +class HashMap { +public: + HashMap(); + HashMap([[clang::noescape]] const Function&); + void ensure(const KeyType&, [[clang::noescape]] const Function&); + bool operator+([[clang::noescape]] const Function&) const; + static void ifAny(HashMap, [[clang::noescape]] const Function&); + +private: + ValueType* m_table { nullptr }; +}; + +} // namespace WTF + +struct A { + static void b(); +}; + +SomeObj* make_obj(); +CFMutableArrayRef make_cf(); + +void someFunction(); +template void call(Callback callback) { + someFunction(); + callback(); +} +void callAsync(const WTF::Function&); + +void raw_ptr() { + SomeObj* obj = make_obj(); + auto foo1 = [obj](){ + // expected-warning@-1{{Captured raw-pointer 'obj' to unretained type is unsafe [alpha.webkit.UnretainedLambdaCapturesChecker]}} + [obj doWork]; + }; + call(foo1); + + auto foo2 = [&obj](){ + // expected-warning@-1{{Captured raw-pointer 'obj' to unretained type is unsafe [alpha.webkit.UnretainedLambdaCapturesChecker]}} + [obj doWork]; + }; + auto foo3 = [&](){ + [obj doWork]; + // expected-warning@-1{{Implicitly captured raw-pointer 'obj' to unretained type is unsafe [alpha.webkit.UnretainedLambdaCapturesChecker]}} + obj = nullptr; + }; + auto foo4 = [=](){ + [obj doWork]; + // expected-warning@-1{{Implicitly captured raw-pointer 'obj' to unretained type is unsafe [alpha.webkit.UnretainedLambdaCapturesChecker]}} + }; + + auto cf = make_cf(); + auto bar1 = [cf](){ + // expected-warning@-1{{Captured reference 'cf' to unretained type is unsafe [alpha.webkit.UnretainedLambdaCapturesChecker]}} + CFArrayAppendValue(cf, nullptr); + }; + auto bar2 = [&cf](){ + // expected-warning@-1{{Captured reference 'cf' to unretained type is unsafe [alpha.webkit.UnretainedLambdaCapturesChecker]}} + CFArrayAppendValue(cf, nullptr); + }; + auto bar3 = [&](){ + CFArrayAppendValue(cf, nullptr); + // expected-warning@-1{{Implicitly captured reference 'cf' to unretained type is unsafe [alpha.webkit.UnretainedLambdaCapturesChecker]}} + cf = nullptr; + }; + auto bar4 = [=](){ + CFArrayAppendValue(cf, nullptr); + // expected-warning@-1{{Implicitly captured reference 'cf' to unretained type is unsafe [alpha.webkit.UnretainedLambdaCapturesChecker]}} + }; + + call(foo1); + call(foo2); + call(foo3); + call(foo4); + + call(bar1); + call(bar2); + call(bar3); + call(bar4); + + // Confirm that the checker respects [[clang::suppress]]. + SomeObj* suppressed_obj = nullptr; + [[clang::suppress]] auto foo5 = [suppressed_obj](){ + [suppressed_obj doWork]; + }; + // no warning. + call(foo5); + + // Confirm that the checker respects [[clang::suppress]]. + CFMutableArrayRef suppressed_cf = nullptr; + [[clang::suppress]] auto bar5 = [suppressed_cf](){ + CFArrayAppendValue(suppressed_cf, nullptr); + }; + // no warning. + call(bar5); +} + +void quiet() { +// This code is not expected to trigger any warnings. + SomeObj *obj; + + auto foo3 = [&]() {}; + auto foo4 = [=]() {}; + + call(foo3); + call(foo4); + + obj = nullptr; +} + +template +void map(SomeObj* start, [[clang::noescape]] Callback&& callback) +{ + while (start) { + callback(start); + start = [start next]; + } +} + +template +void doubleMap(SomeObj* start, [[clang::noescape]] Callback1&& callback1, Callback2&& callback2) +{ + while (start) { + callback1(start); + callback2(start); + start = [start next]; + } +} + +template +void get_count_cf(CFArrayRef array, [[clang::noescape]] Callback1&& callback1, Callback2&& callback2) +{ + auto count = CFArrayGetCount(array); + callback1(count); + callback2(count); +} + +void noescape_lambda() { + SomeObj* someObj = make_obj(); + SomeObj* otherObj = make_obj(); + map(make_obj(), [&](SomeObj *obj) { + [otherObj doWork]; + }); + doubleMap(make_obj(), [&](SomeObj *obj) { + [otherObj doWork]; + }, [&](SomeObj *obj) { + [otherObj doWork]; + // expected-warning@-1{{Implicitly captured raw-pointer 'otherObj' to unretained type is unsafe [alpha.webkit.UnretainedLambdaCapturesChecker]}} + }); + ([&] { + [someObj doWork]; + })(); + + CFMutableArrayRef someCF = make_cf(); + get_count_cf(make_cf(), [&](CFIndex count) { + CFArrayAppendValue(someCF, nullptr); + }, [&](CFIndex count) { + CFArrayAppendValue(someCF, nullptr); + // expected-warning@-1{{Implicitly captured reference 'someCF' to unretained type is unsafe [alpha.webkit.UnretainedLambdaCapturesChecker]}} + }); +} + +void callFunctionOpaque(WTF::Function&&); +void callFunction(WTF::Function&& function) { + someFunction(); + function(); +} + +void lambda_converted_to_function(SomeObj* obj, CFMutableArrayRef cf) +{ + callFunction([&]() { + [obj doWork]; + // expected-warning@-1{{Implicitly captured raw-pointer 'obj' to unretained type is unsafe [alpha.webkit.UnretainedLambdaCapturesChecker]}} + CFArrayAppendValue(cf, nullptr); + // expected-warning@-1{{Implicitly captured reference 'cf' to unretained type is unsafe [alpha.webkit.UnretainedLambdaCapturesChecker]}} + }); + callFunctionOpaque([&]() { + [obj doWork]; + // expected-warning@-1{{Implicitly captured raw-pointer 'obj' to unretained type is unsafe [alpha.webkit.UnretainedLambdaCapturesChecker]}} + CFArrayAppendValue(cf, nullptr); + // expected-warning@-1{{Implicitly captured reference 'cf' to unretained type is unsafe [alpha.webkit.UnretainedLambdaCapturesChecker]}} + }); +} + +@interface ObjWithSelf : NSObject { + RetainPtr delegate; +} +-(void)doWork; +-(void)run; +@end + +@implementation ObjWithSelf +-(void)doWork { + auto doWork = [&] { + someFunction(); + [delegate doWork]; + }; + callFunctionOpaque(doWork); +} +-(void)run { + someFunction(); +} +@end \ No newline at end of file diff --git a/llvm/utils/gn/secondary/clang/lib/StaticAnalyzer/Checkers/BUILD.gn b/llvm/utils/gn/secondary/clang/lib/StaticAnalyzer/Checkers/BUILD.gn index 35886bcc7561a..d371fea8382cd 100644 --- a/llvm/utils/gn/secondary/clang/lib/StaticAnalyzer/Checkers/BUILD.gn +++ b/llvm/utils/gn/secondary/clang/lib/StaticAnalyzer/Checkers/BUILD.gn @@ -146,7 +146,7 @@ static_library("Checkers") { "WebKit/PtrTypesSemantics.cpp", "WebKit/RefCntblBaseVirtualDtorChecker.cpp", "WebKit/RawPtrRefCallArgsChecker.cpp", - "WebKit/UncountedLambdaCapturesChecker.cpp", + "WebKit/RawPtrRefLambdaCapturesChecker.cpp", "WebKit/RawPtrRefLocalVarsChecker.cpp", "cert/InvalidPtrChecker.cpp", ] From 3985c3f3edf75ccb075a65cb4dc60ae99f75a868 Mon Sep 17 00:00:00 2001 From: Ryosuke Niwa Date: Sun, 9 Mar 2025 23:30:08 -0700 Subject: [PATCH 05/31] [alpha.webkit.NoUnretainedMemberChecker] Add a new WebKit checker for unretained member variables and ivars. (#128641) Add a new WebKit checker for member variables and instance variables of NS and CF types. A member variable or instance variable to a CF type should be RetainPtr regardless of whether ARC is enabled or disabled, and that of a NS type should be RetainPtr when ARC is disabled. --- clang/docs/analyzer/checkers.rst | 13 ++ .../clang/StaticAnalyzer/Checkers/Checkers.td | 4 + .../WebKit/RawPtrRefMemberChecker.cpp | 115 +++++++++++++++--- .../Checkers/WebKit/unretained-members-arc.mm | 39 ++++++ .../Checkers/WebKit/unretained-members.mm | 60 +++++++++ 5 files changed, 211 insertions(+), 20 deletions(-) create mode 100644 clang/test/Analysis/Checkers/WebKit/unretained-members-arc.mm create mode 100644 clang/test/Analysis/Checkers/WebKit/unretained-members.mm diff --git a/clang/docs/analyzer/checkers.rst b/clang/docs/analyzer/checkers.rst index 4274acffb053b..088bb18529398 100644 --- a/clang/docs/analyzer/checkers.rst +++ b/clang/docs/analyzer/checkers.rst @@ -3479,6 +3479,19 @@ Raw pointers and references to an object which supports CheckedPtr or CheckedRef See `WebKit Guidelines for Safer C++ Programming `_ for details. +alpha.webkit.NoUnretainedMemberChecker +"""""""""""""""""""""""""""""""""""""""" +Raw pointers and references to a NS or CF object can't be used as class members or ivars. Only RetainPtr is allowed for CF types regardless of whether ARC is enabled or disabled. Only RetainPtr is allowed for NS types when ARC is disabled. + +.. code-block:: cpp + + struct Foo { + NSObject *ptr; // warn + // ... + }; + +See `WebKit Guidelines for Safer C++ Programming `_ for details. + alpha.webkit.UnretainedLambdaCapturesChecker """""""""""""""""""""""""""""""""""""""""""" Raw pointers and references to NS or CF types can't be captured in lambdas. Only RetainPtr is allowed for CF types regardless of whether ARC is enabled or disabled, and only RetainPtr is allowed for NS types when ARC is disabled. diff --git a/clang/include/clang/StaticAnalyzer/Checkers/Checkers.td b/clang/include/clang/StaticAnalyzer/Checkers/Checkers.td index 88a5a52e81a45..d979e0c050865 100644 --- a/clang/include/clang/StaticAnalyzer/Checkers/Checkers.td +++ b/clang/include/clang/StaticAnalyzer/Checkers/Checkers.td @@ -1795,6 +1795,10 @@ def NoUncheckedPtrMemberChecker : Checker<"NoUncheckedPtrMemberChecker">, HelpText<"Check for no unchecked member variables.">, Documentation; +def NoUnretainedMemberChecker : Checker<"NoUnretainedMemberChecker">, + HelpText<"Check for no unretained member variables.">, + Documentation; + def UnretainedLambdaCapturesChecker : Checker<"UnretainedLambdaCapturesChecker">, HelpText<"Check unretained lambda captures.">, Documentation; diff --git a/clang/lib/StaticAnalyzer/Checkers/WebKit/RawPtrRefMemberChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/WebKit/RawPtrRefMemberChecker.cpp index 8357f0599aa4f..d6ab6f4efbf29 100644 --- a/clang/lib/StaticAnalyzer/Checkers/WebKit/RawPtrRefMemberChecker.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/WebKit/RawPtrRefMemberChecker.cpp @@ -31,12 +31,16 @@ class RawPtrRefMemberChecker BugType Bug; mutable BugReporter *BR; +protected: + mutable std::optional RTC; + public: RawPtrRefMemberChecker(const char *description) : Bug(this, description, "WebKit coding guidelines") {} virtual std::optional - isPtrCompatible(const clang::CXXRecordDecl *) const = 0; + isPtrCompatible(const clang::QualType, + const clang::CXXRecordDecl *R) const = 0; virtual bool isPtrCls(const clang::CXXRecordDecl *) const = 0; virtual const char *typeName() const = 0; virtual const char *invariant() const = 0; @@ -58,6 +62,12 @@ class RawPtrRefMemberChecker bool shouldVisitTemplateInstantiations() const { return true; } bool shouldVisitImplicitCode() const { return false; } + bool VisitTypedefDecl(const TypedefDecl *TD) { + if (Checker->RTC) + Checker->RTC->visitTypedef(TD); + return true; + } + bool VisitRecordDecl(const RecordDecl *RD) { Checker->visitRecordDecl(RD); return true; @@ -70,6 +80,8 @@ class RawPtrRefMemberChecker }; LocalVisitor visitor(this); + if (RTC) + RTC->visitTranslationUnitDecl(TUD); visitor.TraverseDecl(const_cast(TUD)); } @@ -78,16 +90,22 @@ class RawPtrRefMemberChecker return; for (auto *Member : RD->fields()) { - const Type *MemberType = Member->getType().getTypePtrOrNull(); + auto QT = Member->getType(); + const Type *MemberType = QT.getTypePtrOrNull(); if (!MemberType) continue; if (auto *MemberCXXRD = MemberType->getPointeeCXXRecordDecl()) { - // If we don't see the definition we just don't know. - if (MemberCXXRD->hasDefinition()) { - std::optional isRCAble = isPtrCompatible(MemberCXXRD); - if (isRCAble && *isRCAble) - reportBug(Member, MemberType, MemberCXXRD, RD); + std::optional IsCompatible = isPtrCompatible(QT, MemberCXXRD); + if (IsCompatible && *IsCompatible) + reportBug(Member, MemberType, MemberCXXRD, RD); + } else { + std::optional IsCompatible = isPtrCompatible(QT, nullptr); + auto *PointeeType = MemberType->getPointeeType().getTypePtrOrNull(); + if (IsCompatible && *IsCompatible) { + auto *Desugared = PointeeType->getUnqualifiedDesugaredType(); + if (auto *ObjCType = dyn_cast_or_null(Desugared)) + reportBug(Member, MemberType, ObjCType->getDecl(), RD); } } } @@ -108,11 +126,12 @@ class RawPtrRefMemberChecker void visitIvarDecl(const ObjCContainerDecl *CD, const ObjCIvarDecl *Ivar) const { - const Type *IvarType = Ivar->getType().getTypePtrOrNull(); + auto QT = Ivar->getType(); + const Type *IvarType = QT.getTypePtrOrNull(); if (!IvarType) return; if (auto *IvarCXXRD = IvarType->getPointeeCXXRecordDecl()) { - std::optional IsCompatible = isPtrCompatible(IvarCXXRD); + std::optional IsCompatible = isPtrCompatible(QT, IvarCXXRD); if (IsCompatible && *IsCompatible) reportBug(Ivar, IvarType, IvarCXXRD, CD); } @@ -152,13 +171,13 @@ class RawPtrRefMemberChecker return false; } - template + template void reportBug(const DeclType *Member, const Type *MemberType, - const CXXRecordDecl *MemberCXXRD, + const PointeeType *Pointee, const ParentDeclType *ClassCXXRD) const { assert(Member); assert(MemberType); - assert(MemberCXXRD); + assert(Pointee); SmallString<100> Buf; llvm::raw_svector_ostream Os(Buf); @@ -170,10 +189,13 @@ class RawPtrRefMemberChecker printQuotedName(Os, Member); Os << " in "; printQuotedQualifiedName(Os, ClassCXXRD); - Os << " is a " - << (isa(MemberType) ? "raw pointer" : "reference") << " to " - << typeName() << " "; - printQuotedQualifiedName(Os, MemberCXXRD); + Os << " is a "; + if (printPointer(Os, MemberType) == PrintDeclKind::Pointer) { + auto Typedef = MemberType->getAs(); + assert(Typedef); + printQuotedQualifiedName(Os, Typedef->getDecl()); + } else + printQuotedQualifiedName(Os, Pointee); Os << "; " << invariant() << "."; PathDiagnosticLocation BSLoc(Member->getSourceRange().getBegin(), @@ -182,6 +204,15 @@ class RawPtrRefMemberChecker Report->addRange(Member->getSourceRange()); BR->emitReport(std::move(Report)); } + + enum class PrintDeclKind { Pointee, Pointer }; + virtual PrintDeclKind printPointer(llvm::raw_svector_ostream &Os, + const Type *T) const { + T = T->getUnqualifiedDesugaredType(); + bool IsPtr = isa(T) || isa(T); + Os << (IsPtr ? "raw pointer" : "reference") << " to " << typeName() << " "; + return PrintDeclKind::Pointee; + } }; class NoUncountedMemberChecker final : public RawPtrRefMemberChecker { @@ -191,8 +222,9 @@ class NoUncountedMemberChecker final : public RawPtrRefMemberChecker { "reference-countable type") {} std::optional - isPtrCompatible(const clang::CXXRecordDecl *R) const final { - return isRefCountable(R); + isPtrCompatible(const clang::QualType, + const clang::CXXRecordDecl *R) const final { + return R && isRefCountable(R); } bool isPtrCls(const clang::CXXRecordDecl *R) const final { @@ -213,8 +245,9 @@ class NoUncheckedPtrMemberChecker final : public RawPtrRefMemberChecker { "checked-pointer capable type") {} std::optional - isPtrCompatible(const clang::CXXRecordDecl *R) const final { - return isCheckedPtrCapable(R); + isPtrCompatible(const clang::QualType, + const clang::CXXRecordDecl *R) const final { + return R && isCheckedPtrCapable(R); } bool isPtrCls(const clang::CXXRecordDecl *R) const final { @@ -229,6 +262,40 @@ class NoUncheckedPtrMemberChecker final : public RawPtrRefMemberChecker { } }; +class NoUnretainedMemberChecker final : public RawPtrRefMemberChecker { +public: + NoUnretainedMemberChecker() + : RawPtrRefMemberChecker("Member variable is a raw-pointer/reference to " + "retainable type") { + RTC = RetainTypeChecker(); + } + + std::optional + isPtrCompatible(const clang::QualType QT, + const clang::CXXRecordDecl *) const final { + return RTC->isUnretained(QT); + } + + bool isPtrCls(const clang::CXXRecordDecl *R) const final { + return isRetainPtr(R); + } + + const char *typeName() const final { return "retainable type"; } + + const char *invariant() const final { + return "member variables must be a RetainPtr"; + } + + PrintDeclKind printPointer(llvm::raw_svector_ostream &Os, + const Type *T) const final { + if (!isa(T) && T->getAs()) { + Os << typeName() << " "; + return PrintDeclKind::Pointer; + } + return RawPtrRefMemberChecker::printPointer(Os, T); + } +}; + } // namespace void ento::registerNoUncountedMemberChecker(CheckerManager &Mgr) { @@ -247,3 +314,11 @@ bool ento::shouldRegisterNoUncheckedPtrMemberChecker( const CheckerManager &Mgr) { return true; } + +void ento::registerNoUnretainedMemberChecker(CheckerManager &Mgr) { + Mgr.registerChecker(); +} + +bool ento::shouldRegisterNoUnretainedMemberChecker(const CheckerManager &Mgr) { + return true; +} diff --git a/clang/test/Analysis/Checkers/WebKit/unretained-members-arc.mm b/clang/test/Analysis/Checkers/WebKit/unretained-members-arc.mm new file mode 100644 index 0000000000000..9820c875b87c0 --- /dev/null +++ b/clang/test/Analysis/Checkers/WebKit/unretained-members-arc.mm @@ -0,0 +1,39 @@ +// RUN: %clang_analyze_cc1 -analyzer-checker=alpha.webkit.NoUnretainedMemberChecker -fobjc-arc -verify %s + +#include "objc-mock-types.h" + +namespace members { + + struct Foo { + private: + SomeObj* a = nullptr; + + [[clang::suppress]] + SomeObj* a_suppressed = nullptr; + + protected: + RetainPtr b; + + public: + SomeObj* c = nullptr; + RetainPtr d; + + CFMutableArrayRef e = nullptr; +// expected-warning@-1{{Member variable 'e' in 'members::Foo' is a retainable type 'CFMutableArrayRef'}} + }; + + template + struct FooTmpl { + T* x; + S y; +// expected-warning@-1{{Member variable 'y' in 'members::FooTmpl' is a raw pointer to retainable type}} + }; + + void forceTmplToInstantiate(FooTmpl) {} + + struct [[clang::suppress]] FooSuppressed { + private: + SomeObj* a = nullptr; + }; + +} diff --git a/clang/test/Analysis/Checkers/WebKit/unretained-members.mm b/clang/test/Analysis/Checkers/WebKit/unretained-members.mm new file mode 100644 index 0000000000000..e068a583c18c5 --- /dev/null +++ b/clang/test/Analysis/Checkers/WebKit/unretained-members.mm @@ -0,0 +1,60 @@ +// RUN: %clang_analyze_cc1 -analyzer-checker=alpha.webkit.NoUnretainedMemberChecker -verify %s + +#include "objc-mock-types.h" + +namespace members { + + struct Foo { + private: + SomeObj* a = nullptr; +// expected-warning@-1{{Member variable 'a' in 'members::Foo' is a raw pointer to retainable type}} + + [[clang::suppress]] + SomeObj* a_suppressed = nullptr; +// No warning. + + protected: + RetainPtr b; +// No warning. + + public: + SomeObj* c = nullptr; +// expected-warning@-1{{Member variable 'c' in 'members::Foo' is a raw pointer to retainable type}} + RetainPtr d; + + CFMutableArrayRef e = nullptr; +// expected-warning@-1{{Member variable 'e' in 'members::Foo' is a retainable type 'CFMutableArrayRef'}} + }; + + template + struct FooTmpl { + T* a; +// expected-warning@-1{{Member variable 'a' in 'members::FooTmpl' is a raw pointer to retainable type}} + S b; +// expected-warning@-1{{Member variable 'b' in 'members::FooTmpl' is a raw pointer to retainable type}} + }; + + void forceTmplToInstantiate(FooTmpl) {} + + struct [[clang::suppress]] FooSuppressed { + private: + SomeObj* a = nullptr; +// No warning. + }; + +} + +namespace ignore_unions { + union Foo { + SomeObj* a; + RetainPtr b; + CFMutableArrayRef c; + }; + + template + union RefPtr { + T* a; + }; + + void forceTmplToInstantiate(RefPtr) {} +} From a322a7e40ed948e61d7c229bf67768804f61fdeb Mon Sep 17 00:00:00 2001 From: Ryosuke Niwa Date: Mon, 10 Mar 2025 21:01:39 -0700 Subject: [PATCH 06/31] [WebKit checkers] Don't treat virtual functions as safe. (#129632) Prior to this PR, WebKit checkers erroneously treated functions to be safe if it has a trivial body even if it was marked as virtual. In the case of a virtual function, it can have an override which does not pass the triviality check so we must not make such an assumption. This PR also restricts the allowed operator overloading while finding the pointer origin to just operators on smart pointer types: Ref, RefPtr, CheckedRef, CheckedPtr, RetainPtr, WeakPtr, WeakRef, unique_ptr, and UniqueRef. --- .../StaticAnalyzer/Checkers/WebKit/ASTUtils.cpp | 16 ++++++++++++---- .../Checkers/WebKit/PtrTypesSemantics.cpp | 4 ++++ .../Checkers/WebKit/PtrTypesSemantics.h | 3 +++ .../WebKit/RawPtrRefCallArgsChecker.cpp | 2 +- .../Checkers/WebKit/uncounted-local-vars.cpp | 13 +++++++++++++ .../Checkers/WebKit/uncounted-obj-arg.cpp | 17 ++++++++++++++++- 6 files changed, 49 insertions(+), 6 deletions(-) diff --git a/clang/lib/StaticAnalyzer/Checkers/WebKit/ASTUtils.cpp b/clang/lib/StaticAnalyzer/Checkers/WebKit/ASTUtils.cpp index c8151e932997e..5e67cb29d08e4 100644 --- a/clang/lib/StaticAnalyzer/Checkers/WebKit/ASTUtils.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/WebKit/ASTUtils.cpp @@ -91,9 +91,17 @@ bool tryToFindPtrOrigin( } if (auto *operatorCall = dyn_cast(E)) { - if (operatorCall->getNumArgs() == 1) { - E = operatorCall->getArg(0); - continue; + if (auto *Callee = operatorCall->getDirectCallee()) { + auto ClsName = safeGetName(Callee->getParent()); + if (isRefType(ClsName) || isCheckedPtr(ClsName) || + isRetainPtr(ClsName) || ClsName == "unique_ptr" || + ClsName == "UniqueRef" || ClsName == "WeakPtr" || + ClsName == "WeakRef") { + if (operatorCall->getNumArgs() == 1) { + E = operatorCall->getArg(0); + continue; + } + } } } @@ -215,7 +223,7 @@ bool EnsureFunctionAnalysis::isACallToEnsureFn(const clang::Expr *E) const { if (!Callee) return false; auto *Body = Callee->getBody(); - if (!Body) + if (!Body || Callee->isVirtualAsWritten()) return false; auto [CacheIt, IsNew] = Cache.insert(std::make_pair(Callee, false)); if (IsNew) diff --git a/clang/lib/StaticAnalyzer/Checkers/WebKit/PtrTypesSemantics.cpp b/clang/lib/StaticAnalyzer/Checkers/WebKit/PtrTypesSemantics.cpp index 8a304a07296fc..419d9c2325412 100644 --- a/clang/lib/StaticAnalyzer/Checkers/WebKit/PtrTypesSemantics.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/WebKit/PtrTypesSemantics.cpp @@ -480,6 +480,10 @@ class TrivialFunctionAnalysisVisitor TrivialFunctionAnalysisVisitor(CacheTy &Cache) : Cache(Cache) {} bool IsFunctionTrivial(const Decl *D) { + if (auto *FnDecl = dyn_cast(D)) { + if (FnDecl->isVirtualAsWritten()) + return false; + } return WithCachedResult(D, [&]() { if (auto *CtorDecl = dyn_cast(D)) { for (auto *CtorInit : CtorDecl->inits()) { diff --git a/clang/lib/StaticAnalyzer/Checkers/WebKit/PtrTypesSemantics.h b/clang/lib/StaticAnalyzer/Checkers/WebKit/PtrTypesSemantics.h index fcc1a41dba78b..323d473665888 100644 --- a/clang/lib/StaticAnalyzer/Checkers/WebKit/PtrTypesSemantics.h +++ b/clang/lib/StaticAnalyzer/Checkers/WebKit/PtrTypesSemantics.h @@ -131,6 +131,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 RetainPtr or its variant, false if not. +bool isRetainPtr(const std::string &Name); + /// \returns true if \p M is getter of a ref-counted class, false if not. std::optional isGetterOfSafePtr(const clang::CXXMethodDecl *Method); diff --git a/clang/lib/StaticAnalyzer/Checkers/WebKit/RawPtrRefCallArgsChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/WebKit/RawPtrRefCallArgsChecker.cpp index aac8a4f100d17..2a4801f4fa466 100644 --- a/clang/lib/StaticAnalyzer/Checkers/WebKit/RawPtrRefCallArgsChecker.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/WebKit/RawPtrRefCallArgsChecker.cpp @@ -175,7 +175,7 @@ class RawPtrRefCallArgsChecker if (BR->getSourceManager().isInSystemHeader(CE->getExprLoc())) return true; - if (Callee && TFA.isTrivial(Callee)) + if (Callee && TFA.isTrivial(Callee) && !Callee->isVirtualAsWritten()) return true; if (CE->getNumArgs() == 0) diff --git a/clang/test/Analysis/Checkers/WebKit/uncounted-local-vars.cpp b/clang/test/Analysis/Checkers/WebKit/uncounted-local-vars.cpp index 52854cd10f68c..07b6de21df80f 100644 --- a/clang/test/Analysis/Checkers/WebKit/uncounted-local-vars.cpp +++ b/clang/test/Analysis/Checkers/WebKit/uncounted-local-vars.cpp @@ -464,4 +464,17 @@ namespace local_var_for_singleton { RefCountable* bar = singleton(); RefCountable* baz = otherSingleton(); } +} + +namespace virtual_function { + struct SomeObject { + virtual RefCountable* provide() { return nullptr; } + virtual RefCountable* operator&() { return nullptr; } + }; + void foo(SomeObject* obj) { + auto* bar = obj->provide(); + // expected-warning@-1{{Local variable 'bar' is uncounted and unsafe [alpha.webkit.UncountedLocalVarsChecker]}} + auto* baz = &*obj; + // expected-warning@-1{{Local variable 'baz' is uncounted and unsafe [alpha.webkit.UncountedLocalVarsChecker]}} + } } \ No newline at end of file diff --git a/clang/test/Analysis/Checkers/WebKit/uncounted-obj-arg.cpp b/clang/test/Analysis/Checkers/WebKit/uncounted-obj-arg.cpp index fe7ce158eb8ba..0279e2c68ec6d 100644 --- a/clang/test/Analysis/Checkers/WebKit/uncounted-obj-arg.cpp +++ b/clang/test/Analysis/Checkers/WebKit/uncounted-obj-arg.cpp @@ -196,6 +196,10 @@ class ComplexNumber { ComplexNumber& operator+(); const Number& real() const { return realPart; } + const Number& complex() const; + + void ref() const; + void deref() const; private: Number realPart; @@ -240,6 +244,11 @@ class SomeType : public BaseType { using BaseType::BaseType; }; +struct OtherObj { + unsigned v { 0 }; + OtherObj* children[4] { nullptr }; +}; + void __libcpp_verbose_abort(const char *__format, ...); class RefCounted { @@ -375,7 +384,7 @@ class RefCounted { double y; }; void trivial68() { point pt = { 1.0 }; } - unsigned trivial69() { return offsetof(RefCounted, children); } + unsigned trivial69() { return offsetof(OtherObj, children); } DerivedNumber* trivial70() { [[clang::suppress]] return static_cast(number); } static RefCounted& singleton() { @@ -467,6 +476,8 @@ class RefCounted { unsigned nonTrivial22() { return ComplexNumber(123, "456").real().value(); } unsigned nonTrivial23() { return DerivedNumber("123").value(); } SomeType nonTrivial24() { return SomeType("123"); } + virtual void nonTrivial25() { } + virtual ComplexNumber* operator->() { return nullptr; } static unsigned s_v; unsigned v { 0 }; @@ -642,6 +653,10 @@ class UnrelatedClass { // expected-warning@-1{{Call argument for 'this' parameter is uncounted and unsafe}} getFieldTrivial().nonTrivial24(); // expected-warning@-1{{Call argument for 'this' parameter is uncounted and unsafe}} + getFieldTrivial().nonTrivial25(); + // expected-warning@-1{{Call argument for 'this' parameter is uncounted and unsafe}} + getFieldTrivial()->complex(); + // expected-warning@-1{{Call argument for 'this' parameter is uncounted and unsafe}} } void setField(RefCounted*); From d3dbb0524ec1a64296fc4f111b43e54aa99c46a0 Mon Sep 17 00:00:00 2001 From: Ryosuke Niwa Date: Wed, 12 Mar 2025 10:23:46 -0700 Subject: [PATCH 07/31] [webkit.UncountedLambdaCapturesChecker] Recognize std::move(protectedThis) (#130925) In WebKit, it's a common pattern for a lambda to capture "this" along with "protectedThis" of Ref/RefPtr type, and re-capture "this" and "std::move(protectedThis)" for a nested inner lambda. Recognize this pattern and treat it as safe. --- .../WebKit/RawPtrRefLambdaCapturesChecker.cpp | 26 +++++++++++++++---- .../WebKit/uncounted-lambda-captures.cpp | 11 +++++++- 2 files changed, 31 insertions(+), 6 deletions(-) diff --git a/clang/lib/StaticAnalyzer/Checkers/WebKit/RawPtrRefLambdaCapturesChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/WebKit/RawPtrRefLambdaCapturesChecker.cpp index a45a0ed89d7dc..fdbd8e2922438 100644 --- a/clang/lib/StaticAnalyzer/Checkers/WebKit/RawPtrRefLambdaCapturesChecker.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/WebKit/RawPtrRefLambdaCapturesChecker.cpp @@ -285,15 +285,31 @@ class RawPtrRefLambdaCapturesChecker do { if (auto *BTE = dyn_cast(Arg)) Arg = BTE->getSubExpr()->IgnoreParenCasts(); - if (auto *CE = dyn_cast_or_null(Arg)) { + if (auto *CE = dyn_cast(Arg)) { auto *Ctor = CE->getConstructor(); if (!Ctor) return false; auto clsName = safeGetName(Ctor->getParent()); - if (!isRefType(clsName) || !CE->getNumArgs()) - return false; - Arg = CE->getArg(0)->IgnoreParenCasts(); - continue; + if (isRefType(clsName) && CE->getNumArgs()) { + Arg = CE->getArg(0)->IgnoreParenCasts(); + continue; + } + if (auto *Type = ClsType.getTypePtrOrNull()) { + if (auto *CXXR = Type->getPointeeCXXRecordDecl()) { + if (CXXR == Ctor->getParent() && Ctor->isMoveConstructor() && + CE->getNumArgs() == 1) { + Arg = CE->getArg(0)->IgnoreParenCasts(); + continue; + } + } + } + return false; + } + if (auto *CE = dyn_cast(Arg)) { + if (CE->isCallToStdMove() && CE->getNumArgs() == 1) { + Arg = CE->getArg(0)->IgnoreParenCasts(); + continue; + } } if (auto *OpCE = dyn_cast(Arg)) { auto OpCode = OpCE->getOperator(); diff --git a/clang/test/Analysis/Checkers/WebKit/uncounted-lambda-captures.cpp b/clang/test/Analysis/Checkers/WebKit/uncounted-lambda-captures.cpp index 1746d2b93d469..36135973e78c0 100644 --- a/clang/test/Analysis/Checkers/WebKit/uncounted-lambda-captures.cpp +++ b/clang/test/Analysis/Checkers/WebKit/uncounted-lambda-captures.cpp @@ -2,6 +2,15 @@ #include "mock-types.h" +namespace std { + +template +T&& move(T& t) { + return static_cast(t); +} + +} + namespace WTF { namespace Detail { @@ -321,7 +330,7 @@ struct RefCountableWithLambdaCapturingThis { void method_nested_lambda2() { callAsync([this, protectedThis = RefPtr { this }] { - callAsync([this, protectedThis = static_cast&&>(*protectedThis)] { + callAsync([this, protectedThis = std::move(*protectedThis)] { nonTrivial(); }); }); From b30d426ad323342b9ab62189b15b41fd83497cda Mon Sep 17 00:00:00 2001 From: Ryosuke Niwa Date: Wed, 12 Mar 2025 10:37:13 -0700 Subject: [PATCH 08/31] Add unretained call args checker (#130901) Reland https://github.com/llvm/llvm-project/pull/130729 --- clang/docs/analyzer/checkers.rst | 6 + .../clang/StaticAnalyzer/Checkers/Checkers.td | 4 + .../Checkers/WebKit/ASTUtils.cpp | 38 ++ .../Checkers/WebKit/PtrTypesSemantics.cpp | 6 +- .../WebKit/RawPtrRefCallArgsChecker.cpp | 136 +++++- .../WebKit/unretained-call-args-arc.mm | 30 ++ .../Checkers/WebKit/unretained-call-args.mm | 396 ++++++++++++++++++ 7 files changed, 608 insertions(+), 8 deletions(-) create mode 100644 clang/test/Analysis/Checkers/WebKit/unretained-call-args-arc.mm create mode 100644 clang/test/Analysis/Checkers/WebKit/unretained-call-args.mm diff --git a/clang/docs/analyzer/checkers.rst b/clang/docs/analyzer/checkers.rst index 088bb18529398..c96e0c6d9b78f 100644 --- a/clang/docs/analyzer/checkers.rst +++ b/clang/docs/analyzer/checkers.rst @@ -3599,6 +3599,12 @@ The goal of this rule is to make sure that lifetime of any dynamically allocated The rules of when to use and not to use CheckedPtr / CheckedRef are same as alpha.webkit.UncountedCallArgsChecker for ref-counted objects. +alpha.webkit.UnretainedCallArgsChecker +"""""""""""""""""""""""""""""""""""""" +The goal of this rule is to make sure that lifetime of any dynamically allocated NS or CF objects passed as a call argument keeps its memory region past the end of the call. This applies to call to any function, method, lambda, function pointer or functor. NS or CF objects aren't supposed to be allocated on stack so we check arguments for parameters of raw pointers and references to unretained types. + +The rules of when to use and not to use RetainPtr are same as alpha.webkit.UncountedCallArgsChecker for ref-counted objects. + alpha.webkit.UncountedLocalVarsChecker """""""""""""""""""""""""""""""""""""" The goal of this rule is to make sure that any uncounted local variable is backed by a ref-counted object with lifetime that is strictly larger than the scope of the uncounted local variable. To be on the safe side we require the scope of an uncounted variable to be embedded in the scope of ref-counted object that backs it. diff --git a/clang/include/clang/StaticAnalyzer/Checkers/Checkers.td b/clang/include/clang/StaticAnalyzer/Checkers/Checkers.td index d979e0c050865..ebe95670275c3 100644 --- a/clang/include/clang/StaticAnalyzer/Checkers/Checkers.td +++ b/clang/include/clang/StaticAnalyzer/Checkers/Checkers.td @@ -1811,6 +1811,10 @@ def UncheckedCallArgsChecker : Checker<"UncheckedCallArgsChecker">, HelpText<"Check unchecked call arguments.">, Documentation; +def UnretainedCallArgsChecker : Checker<"UnretainedCallArgsChecker">, + HelpText<"Check unretained call arguments.">, + Documentation; + def UncountedLocalVarsChecker : Checker<"UncountedLocalVarsChecker">, HelpText<"Check uncounted local variables.">, Documentation; diff --git a/clang/lib/StaticAnalyzer/Checkers/WebKit/ASTUtils.cpp b/clang/lib/StaticAnalyzer/Checkers/WebKit/ASTUtils.cpp index 5e67cb29d08e4..ae0f8ad09f26e 100644 --- a/clang/lib/StaticAnalyzer/Checkers/WebKit/ASTUtils.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/WebKit/ASTUtils.cpp @@ -8,6 +8,7 @@ #include "ASTUtils.h" #include "PtrTypesSemantics.h" +#include "clang/AST/Attr.h" #include "clang/AST/CXXInheritance.h" #include "clang/AST/Decl.h" #include "clang/AST/DeclCXX.h" @@ -28,6 +29,15 @@ bool tryToFindPtrOrigin( std::function isSafePtrType, std::function callback) { while (E) { + if (auto *DRE = dyn_cast(E)) { + auto *ValDecl = DRE->getDecl(); + auto QT = ValDecl->getType(); + auto ValName = ValDecl->getName(); + if (ValDecl && (ValName.starts_with('k') || ValName.starts_with("_k")) && + QT.isConstQualified()) { // Treat constants such as kCF* as safe. + return callback(E, true); + } + } if (auto *tempExpr = dyn_cast(E)) { E = tempExpr->getSubExpr(); continue; @@ -57,6 +67,10 @@ bool tryToFindPtrOrigin( E = tempExpr->getSubExpr(); continue; } + if (auto *OpaqueValue = dyn_cast(E)) { + E = OpaqueValue->getSourceExpr(); + continue; + } if (auto *Expr = dyn_cast(E)) { return tryToFindPtrOrigin(Expr->getTrueExpr(), StopAtFirstRefCountedObj, isSafePtr, isSafePtrType, callback) && @@ -129,6 +143,11 @@ bool tryToFindPtrOrigin( E = call->getArg(0); continue; } + + auto Name = safeGetName(callee); + if (Name == "__builtin___CFStringMakeConstantString" || + Name == "NSClassFromString") + return callback(E, true); } } if (auto *ObjCMsgExpr = dyn_cast(E)) { @@ -136,7 +155,18 @@ bool tryToFindPtrOrigin( if (isSafePtrType(Method->getReturnType())) return callback(E, true); } + auto Selector = ObjCMsgExpr->getSelector(); + auto NameForFirstSlot = Selector.getNameForSlot(0); + if ((NameForFirstSlot == "class" || NameForFirstSlot == "superclass") && + !Selector.getNumArgs()) + return callback(E, true); } + if (auto *ObjCDict = dyn_cast(E)) + return callback(ObjCDict, true); + if (auto *ObjCArray = dyn_cast(E)) + return callback(ObjCArray, true); + if (auto *ObjCStr = dyn_cast(E)) + return callback(ObjCStr, true); if (auto *unaryOp = dyn_cast(E)) { // FIXME: Currently accepts ANY unary operator. Is it OK? E = unaryOp->getSubExpr(); @@ -156,6 +186,14 @@ bool isASafeCallArg(const Expr *E) { if (auto *D = dyn_cast_or_null(FoundDecl)) { if (isa(D) || D->isLocalVarDecl()) return true; + if (auto *ImplicitP = dyn_cast(D)) { + auto Kind = ImplicitP->getParameterKind(); + if (Kind == ImplicitParamKind::ObjCSelf || + Kind == ImplicitParamKind::ObjCCmd || + Kind == ImplicitParamKind::CXXThis || + Kind == ImplicitParamKind::CXXVTT) + return true; + } } else if (auto *BD = dyn_cast_or_null(FoundDecl)) { VarDecl *VD = BD->getHoldingVar(); if (VD && (isa(VD) || VD->isLocalVarDecl())) diff --git a/clang/lib/StaticAnalyzer/Checkers/WebKit/PtrTypesSemantics.cpp b/clang/lib/StaticAnalyzer/Checkers/WebKit/PtrTypesSemantics.cpp index 419d9c2325412..e780f610eb389 100644 --- a/clang/lib/StaticAnalyzer/Checkers/WebKit/PtrTypesSemantics.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/WebKit/PtrTypesSemantics.cpp @@ -372,7 +372,8 @@ std::optional isGetterOfSafePtr(const CXXMethodDecl *M) { if (auto *maybeRefToRawOperator = dyn_cast(M)) { auto QT = maybeRefToRawOperator->getConversionType(); auto *T = QT.getTypePtrOrNull(); - return T && (T->isPointerType() || T->isReferenceType()); + return T && (T->isPointerType() || T->isReferenceType() || + T->isObjCObjectPointerType()); } } } @@ -415,7 +416,8 @@ bool isPtrConversion(const FunctionDecl *F) { if (FunctionName == "getPtr" || FunctionName == "WeakPtr" || FunctionName == "dynamicDowncast" || FunctionName == "downcast" || FunctionName == "checkedDowncast" || - FunctionName == "uncheckedDowncast" || FunctionName == "bitwise_cast") + FunctionName == "uncheckedDowncast" || FunctionName == "bitwise_cast" || + FunctionName == "bridge_cast") return true; return false; diff --git a/clang/lib/StaticAnalyzer/Checkers/WebKit/RawPtrRefCallArgsChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/WebKit/RawPtrRefCallArgsChecker.cpp index 2a4801f4fa466..b825be2dea8b1 100644 --- a/clang/lib/StaticAnalyzer/Checkers/WebKit/RawPtrRefCallArgsChecker.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/WebKit/RawPtrRefCallArgsChecker.cpp @@ -13,6 +13,7 @@ #include "clang/AST/Decl.h" #include "clang/AST/DeclCXX.h" #include "clang/AST/RecursiveASTVisitor.h" +#include "clang/Analysis/DomainSpecific/CocoaConventions.h" #include "clang/Basic/SourceLocation.h" #include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h" #include "clang/StaticAnalyzer/Core/BugReporter/BugReporter.h" @@ -35,6 +36,9 @@ class RawPtrRefCallArgsChecker TrivialFunctionAnalysis TFA; EnsureFunctionAnalysis EFA; +protected: + mutable std::optional RTC; + public: RawPtrRefCallArgsChecker(const char *description) : Bug(this, description, "WebKit coding guidelines") {} @@ -83,9 +87,22 @@ class RawPtrRefCallArgsChecker Checker->visitCallExpr(CE, DeclWithIssue); return true; } + + bool VisitTypedefDecl(TypedefDecl *TD) { + if (Checker->RTC) + Checker->RTC->visitTypedef(TD); + return true; + } + + bool VisitObjCMessageExpr(ObjCMessageExpr *ObjCMsgExpr) { + Checker->visitObjCMessageExpr(ObjCMsgExpr, DeclWithIssue); + return true; + } }; LocalVisitor visitor(this); + if (RTC) + RTC->visitTranslationUnitDecl(TUD); visitor.TraverseDecl(const_cast(TUD)); } @@ -125,7 +142,7 @@ class RawPtrRefCallArgsChecker // if ((*P)->hasAttr()) // continue; - QualType ArgType = (*P)->getType().getCanonicalType(); + QualType ArgType = (*P)->getType(); // FIXME: more complex types (arrays, references to raw pointers, etc) std::optional IsUncounted = isUnsafePtr(ArgType); if (!IsUncounted || !(*IsUncounted)) @@ -141,6 +158,58 @@ class RawPtrRefCallArgsChecker reportBug(Arg, *P, D); } + for (; ArgIdx < CE->getNumArgs(); ++ArgIdx) { + const auto *Arg = CE->getArg(ArgIdx); + auto ArgType = Arg->getType(); + std::optional IsUncounted = isUnsafePtr(ArgType); + if (!IsUncounted || !(*IsUncounted)) + continue; + + if (auto *defaultArg = dyn_cast(Arg)) + Arg = defaultArg->getExpr(); + + if (isPtrOriginSafe(Arg)) + continue; + + reportBug(Arg, nullptr, D); + } + } + } + + void visitObjCMessageExpr(const ObjCMessageExpr *E, const Decl *D) const { + if (BR->getSourceManager().isInSystemHeader(E->getExprLoc())) + return; + + auto Selector = E->getSelector(); + if (auto *Receiver = E->getInstanceReceiver()) { + std::optional IsUnsafe = isUnsafePtr(E->getReceiverType()); + if (IsUnsafe && *IsUnsafe && !isPtrOriginSafe(Receiver)) { + if (auto *InnerMsg = dyn_cast(Receiver)) { + auto InnerSelector = InnerMsg->getSelector(); + if (InnerSelector.getNameForSlot(0) == "alloc" && + Selector.getNameForSlot(0).starts_with("init")) + return; + } + reportBugOnReceiver(Receiver, D); + } + } + + auto *MethodDecl = E->getMethodDecl(); + if (!MethodDecl) + return; + + auto ArgCount = E->getNumArgs(); + for (unsigned i = 0; i < ArgCount; ++i) { + auto *Arg = E->getArg(i); + bool hasParam = i < MethodDecl->param_size(); + auto *Param = hasParam ? MethodDecl->getParamDecl(i) : nullptr; + auto ArgType = Arg->getType(); + std::optional IsUnsafe = isUnsafePtr(ArgType); + if (!IsUnsafe || !(*IsUnsafe)) + continue; + if (isPtrOriginSafe(Arg)) + continue; + reportBug(Arg, Param, D); } } @@ -161,6 +230,8 @@ class RawPtrRefCallArgsChecker // foo(NULL) return true; } + if (isa(ArgOrigin)) + return true; if (isASafeCallArg(ArgOrigin)) return true; if (EFA.isACallToEnsureFn(ArgOrigin)) @@ -215,7 +286,7 @@ class RawPtrRefCallArgsChecker overloadedOperatorType == OO_PipePipe) return true; - if (isCtorOfRefCounted(Callee)) + if (isCtorOfSafePtr(Callee)) return true; auto name = safeGetName(Callee); @@ -280,9 +351,10 @@ class RawPtrRefCallArgsChecker } Os << " is " << ptrKind() << " and unsafe."; + bool usesDefaultArgValue = isa(CallArg) && Param; const SourceLocation SrcLocToReport = - isa(CallArg) ? Param->getDefaultArg()->getExprLoc() - : CallArg->getSourceRange().getBegin(); + usesDefaultArgValue ? Param->getDefaultArg()->getExprLoc() + : CallArg->getSourceRange().getBegin(); PathDiagnosticLocation BSLoc(SrcLocToReport, BR->getSourceManager()); auto Report = std::make_unique(Bug, Os.str(), BSLoc); @@ -307,6 +379,23 @@ class RawPtrRefCallArgsChecker Report->setDeclWithIssue(DeclWithIssue); BR->emitReport(std::move(Report)); } + + void reportBugOnReceiver(const Expr *CallArg, + const Decl *DeclWithIssue) const { + assert(CallArg); + + const SourceLocation SrcLocToReport = CallArg->getSourceRange().getBegin(); + + SmallString<100> Buf; + llvm::raw_svector_ostream Os(Buf); + Os << "Reciever is " << ptrKind() << " and unsafe."; + + PathDiagnosticLocation BSLoc(SrcLocToReport, BR->getSourceManager()); + auto Report = std::make_unique(Bug, Os.str(), BSLoc); + Report->addRange(CallArg->getSourceRange()); + Report->setDeclWithIssue(DeclWithIssue); + BR->emitReport(std::move(Report)); + } }; class UncountedCallArgsChecker final : public RawPtrRefCallArgsChecker { @@ -320,7 +409,7 @@ class UncountedCallArgsChecker final : public RawPtrRefCallArgsChecker { } std::optional isUnsafePtr(QualType QT) const final { - return isUncountedPtr(QT); + return isUncountedPtr(QT.getCanonicalType()); } bool isSafePtr(const CXXRecordDecl *Record) const final { @@ -345,7 +434,7 @@ class UncheckedCallArgsChecker final : public RawPtrRefCallArgsChecker { } std::optional isUnsafePtr(QualType QT) const final { - return isUncheckedPtr(QT); + return isUncheckedPtr(QT.getCanonicalType()); } bool isSafePtr(const CXXRecordDecl *Record) const final { @@ -359,6 +448,33 @@ class UncheckedCallArgsChecker final : public RawPtrRefCallArgsChecker { const char *ptrKind() const final { return "unchecked"; } }; +class UnretainedCallArgsChecker final : public RawPtrRefCallArgsChecker { +public: + UnretainedCallArgsChecker() + : RawPtrRefCallArgsChecker("Unretained call argument for a raw " + "pointer/reference parameter") { + RTC = RetainTypeChecker(); + } + + std::optional isUnsafeType(QualType QT) const final { + return RTC->isUnretained(QT); + } + + std::optional isUnsafePtr(QualType QT) const final { + return RTC->isUnretained(QT); + } + + bool isSafePtr(const CXXRecordDecl *Record) const final { + return isRetainPtr(Record); + } + + bool isSafePtrType(const QualType type) const final { + return isRetainPtrType(type); + } + + const char *ptrKind() const final { return "unretained"; } +}; + } // namespace void ento::registerUncountedCallArgsChecker(CheckerManager &Mgr) { @@ -376,3 +492,11 @@ void ento::registerUncheckedCallArgsChecker(CheckerManager &Mgr) { bool ento::shouldRegisterUncheckedCallArgsChecker(const CheckerManager &) { return true; } + +void ento::registerUnretainedCallArgsChecker(CheckerManager &Mgr) { + Mgr.registerChecker(); +} + +bool ento::shouldRegisterUnretainedCallArgsChecker(const CheckerManager &) { + return true; +} diff --git a/clang/test/Analysis/Checkers/WebKit/unretained-call-args-arc.mm b/clang/test/Analysis/Checkers/WebKit/unretained-call-args-arc.mm new file mode 100644 index 0000000000000..eb4735da60a05 --- /dev/null +++ b/clang/test/Analysis/Checkers/WebKit/unretained-call-args-arc.mm @@ -0,0 +1,30 @@ +// RUN: %clang_analyze_cc1 -analyzer-checker=alpha.webkit.UnretainedCallArgsChecker -fobjc-arc -verify %s + +#import "objc-mock-types.h" + +SomeObj *provide(); +CFMutableArrayRef provide_cf(); +void someFunction(); + +namespace raw_ptr { + +void foo() { + [provide() doWork]; + CFArrayAppendValue(provide_cf(), nullptr); + // expected-warning@-1{{Call argument for parameter 'theArray' is unretained and unsafe [alpha.webkit.UnretainedCallArgsChecker]}} +} + +} // namespace raw_ptr + +@interface AnotherObj : NSObject +- (void)foo:(SomeObj *)obj; +@end + +@implementation AnotherObj +- (void)foo:(SomeObj*)obj { + [obj doWork]; + [provide() doWork]; + CFArrayAppendValue(provide_cf(), nullptr); + // expected-warning@-1{{Call argument for parameter 'theArray' is unretained and unsafe [alpha.webkit.UnretainedCallArgsChecker]}} +} +@end diff --git a/clang/test/Analysis/Checkers/WebKit/unretained-call-args.mm b/clang/test/Analysis/Checkers/WebKit/unretained-call-args.mm new file mode 100644 index 0000000000000..3411cbdf5aa9b --- /dev/null +++ b/clang/test/Analysis/Checkers/WebKit/unretained-call-args.mm @@ -0,0 +1,396 @@ +// RUN: %clang_analyze_cc1 -analyzer-checker=alpha.webkit.UnretainedCallArgsChecker -verify %s + +#include "objc-mock-types.h" + +SomeObj *provide(); +void consume_obj(SomeObj*); + +CFMutableArrayRef provide_cf(); +void consume_cf(CFMutableArrayRef); + +void some_function(); + +namespace simple { + void foo() { + consume_obj(provide()); + // expected-warning@-1{{Call argument is unretained and unsafe}} + consume_cf(provide_cf()); + // expected-warning@-1{{Call argument is unretained and unsafe}} + } + + // Test that the checker works with [[clang::suppress]]. + void foo_suppressed() { + [[clang::suppress]] consume_obj(provide()); // no-warning + [[clang::suppress]] consume_cf(provide_cf()); // no-warning + } + +} + +namespace multi_arg { + void consume_retainable(int, SomeObj* foo, CFMutableArrayRef bar, bool); + void foo() { + consume_retainable(42, provide(), provide_cf(), true); + // expected-warning@-1{{Call argument for parameter 'foo' is unretained and unsafe}} + // expected-warning@-2{{Call argument for parameter 'bar' is unretained and unsafe}} + } + + void consume_retainable(SomeObj* foo, ...); + void bar() { + consume_retainable(provide(), 1, provide_cf(), RetainPtr { provide_cf() }.get()); + // expected-warning@-1{{Call argument for parameter 'foo' is unretained and unsafe}} + // expected-warning@-2{{Call argument is unretained and unsafe}} + consume_retainable(RetainPtr { provide() }.get(), 1, RetainPtr { provide_cf() }.get()); + } +} + +namespace retained { + RetainPtr provide_obj() { return RetainPtr{}; } + void consume_obj(RetainPtr) {} + + RetainPtr provide_cf() { return CFMutableArrayRef{}; } + void consume_cf(RetainPtr) {} + + void foo() { + consume_obj(provide_obj().get()); // no warning + consume_cf(provide_cf().get()); // no warning + } +} + +namespace methods { + struct Consumer { + void consume_obj(SomeObj* ptr); + void consume_cf(CFMutableArrayRef ref); + }; + + void foo() { + Consumer c; + + c.consume_obj(provide()); + // expected-warning@-1{{Call argument for parameter 'ptr' is unretained and unsafe}} + c.consume_cf(provide_cf()); + // expected-warning@-1{{Call argument for parameter 'ref' is unretained and unsafe}} + } + + void foo2() { + struct Consumer { + void consume(SomeObj*) { some_function(); } + void whatever() { + consume(provide()); + // expected-warning@-1{{Call argument is unretained and unsafe}} + } + + void consume_cf(CFMutableArrayRef) { some_function(); } + void something() { + consume_cf(provide_cf()); + // expected-warning@-1{{Call argument is unretained and unsafe}} + } + }; + } + + void foo3() { + struct Consumer { + void consume(SomeObj*) { some_function(); } + void whatever() { + this->consume(provide()); + // expected-warning@-1{{Call argument is unretained and unsafe}} + } + + void consume_cf(CFMutableArrayRef) { some_function(); } + void something() { + this->consume_cf(provide_cf()); + // expected-warning@-1{{Call argument is unretained and unsafe}} + } + }; + } + +} + +namespace casts { + void foo() { + consume_obj(provide()); + // expected-warning@-1{{Call argument is unretained and unsafe}} + + consume_obj(static_cast(provide())); + // expected-warning@-1{{Call argument is unretained and unsafe}} + + consume_obj(reinterpret_cast(provide())); + // expected-warning@-1{{Call argument is unretained and unsafe}} + + consume_obj(downcast(provide())); + // expected-warning@-1{{Call argument is unretained and unsafe}} + } +} + +namespace null_ptr { + void foo_ref() { + consume_obj(nullptr); + consume_obj(0); + consume_cf(nullptr); + consume_cf(0); + } +} + +namespace retain_ptr_lookalike { + struct Decoy { + SomeObj* get(); + }; + + void foo() { + Decoy D; + + consume_obj(D.get()); + // expected-warning@-1{{Call argument is unretained and unsafe}} + } + + struct Decoy2 { + CFMutableArrayRef get(); + }; + + void bar() { + Decoy2 D; + + consume_cf(D.get()); + // expected-warning@-1{{Call argument is unretained and unsafe}} + } +} + +namespace param_formarding_function { + void consume_more_obj(OtherObj*); + void consume_more_cf(CFMutableArrayRef); + + namespace objc { + void foo(SomeObj* param) { + consume_more_obj(downcast(param)); + } + } + + namespace cf { + void foo(CFMutableArrayRef param) { + consume_more_cf(param); + } + } +} + +namespace param_formarding_lambda { + auto consume_more_obj = [](OtherObj*) { some_function(); }; + auto consume_more_cf = [](CFMutableArrayRef) { some_function(); }; + + namespace objc { + void foo(SomeObj* param) { + consume_more_obj(downcast(param)); + } + } + + namespace cf { + void foo(CFMutableArrayRef param) { + consume_more_cf(param); + } + } +} + +namespace param_forwarding_method { + struct Consumer { + void consume_obj(SomeObj*); + static void consume_obj_s(SomeObj*); + void consume_cf(CFMutableArrayRef); + static void consume_cf_s(CFMutableArrayRef); + }; + + void bar(Consumer* consumer, SomeObj* param) { + consumer->consume_obj(param); + } + + void foo(SomeObj* param) { + Consumer::consume_obj_s(param); + } + + void baz(Consumer* consumer, CFMutableArrayRef param) { + consumer->consume_cf(param); + Consumer::consume_cf_s(param); + } +} + + +namespace default_arg { + SomeObj* global; + CFMutableArrayRef global_cf; + + void function_with_default_arg1(SomeObj* param = global); + // expected-warning@-1{{Call argument for parameter 'param' is unretained and unsafe}} + + void function_with_default_arg2(CFMutableArrayRef param = global_cf); + // expected-warning@-1{{Call argument for parameter 'param' is unretained and unsafe}} + + void foo() { + function_with_default_arg1(); + function_with_default_arg2(); + } +} + +namespace cxx_member_func { + RetainPtr protectedProvide(); + RetainPtr protectedProvideCF(); + + void foo() { + [provide() doWork]; + // expected-warning@-1{{Reciever is unretained and unsafe}} + [protectedProvide().get() doWork]; + + CFArrayAppendValue(provide_cf(), nullptr); + // expected-warning@-1{{Call argument for parameter 'theArray' is unretained and unsafe}} + CFArrayAppendValue(protectedProvideCF(), nullptr); + }; + + void bar() { + [downcast(protectedProvide().get()) doMoreWork:downcast(provide())]; + // expected-warning@-1{{Call argument for parameter 'other' is unretained and unsafe}} + [protectedProvide().get() doWork]; + }; + +} + +namespace cxx_member_operator_call { + // The hidden this-pointer argument without a corresponding parameter caused couple bugs in parameter <-> argument attribution. + struct Foo { + Foo& operator+(SomeObj* bad); + friend Foo& operator-(Foo& lhs, SomeObj* bad); + void operator()(SomeObj* bad); + }; + + SomeObj* global; + + void foo() { + Foo f; + f + global; + // expected-warning@-1{{Call argument for parameter 'bad' is unretained and unsafe}} + f - global; + // expected-warning@-1{{Call argument for parameter 'bad' is unretained and unsafe}} + f(global); + // expected-warning@-1{{Call argument for parameter 'bad' is unretained and unsafe}} + } +} + +namespace call_with_ptr_on_ref { + RetainPtr provideProtected(); + RetainPtr provideProtectedCF(); + void bar(SomeObj* bad); + void bar_cf(CFMutableArrayRef bad); + bool baz(); + void foo(bool v) { + bar(v ? nullptr : provideProtected().get()); + bar(baz() ? provideProtected().get() : nullptr); + bar(v ? provide() : provideProtected().get()); + // expected-warning@-1{{Call argument for parameter 'bad' is unretained and unsafe}} + bar(v ? provideProtected().get() : provide()); + // expected-warning@-1{{Call argument for parameter 'bad' is unretained and unsafe}} + + bar_cf(v ? nullptr : provideProtectedCF().get()); + bar_cf(baz() ? provideProtectedCF().get() : nullptr); + bar_cf(v ? provide_cf() : provideProtectedCF().get()); + // expected-warning@-1{{Call argument for parameter 'bad' is unretained and unsafe}} + bar_cf(v ? provideProtectedCF().get() : provide_cf()); + // expected-warning@-1{{Call argument for parameter 'bad' is unretained and unsafe}} + } +} + +namespace call_with_explicit_temporary_obj { + void foo() { + [RetainPtr(provide()).get() doWork]; + CFArrayAppendValue(RetainPtr { provide_cf() }.get(), nullptr); + } + template + void bar() { + [RetainPtr(provide()).get() doWork]; + CFArrayAppendValue(RetainPtr { provide_cf() }.get(), nullptr); + } + void baz() { + bar(); + } +} + +namespace call_with_adopt_ref { + void foo() { + [adoptNS(provide()).get() doWork]; + CFArrayAppendValue(adoptCF(provide_cf()).get(), nullptr); + } +} + +namespace call_with_cf_constant { + void bar(const NSArray *); + void baz(const NSDictionary *); + void foo() { + CFArrayCreateMutable(kCFAllocatorDefault, 10); + bar(@[@"hello"]); + baz(@{@"hello": @3}); + } +} + +namespace call_with_cf_string { + void bar(CFStringRef); + void foo() { + bar(CFSTR("hello")); + } +} + +namespace call_with_ns_string { + void bar(NSString *); + void foo() { + bar(@"world"); + } +} + +namespace bridge_cast_arg { + void bar(NSString *); + void baz(NSString *); + extern const CFStringRef kCFBundleNameKey; + + NSObject *foo(CFStringRef arg) { + bar((NSString *)bridge_cast((CFTypeRef)arg)); + auto dict = @{ + @"hello": @1, + }; + return dict[(__bridge NSString *)kCFBundleNameKey]; + } +} + +namespace alloc_init_pair { + void foo() { + auto obj = adoptNS([[SomeObj alloc] init]); + [obj doWork]; + } +} + +namespace alloc_class { + bool foo(NSObject *obj) { + return [obj isKindOfClass:SomeObj.class] && [obj isKindOfClass:NSClassFromString(@"SomeObj")]; + } + + bool bar(NSObject *obj) { + return [obj isKindOfClass:[SomeObj class]]; + } + + bool baz(NSObject *obj) { + return [obj isKindOfClass:[SomeObj superclass]]; + } +} + +@interface TestObject : NSObject +- (void)doWork:(NSString *)msg, ...; +- (void)doWorkOnSelf; +@end + +@implementation TestObject + +- (void)doWork:(NSString *)msg, ... { + some_function(); +} + +- (void)doWorkOnSelf { + [self doWork:nil]; + [self doWork:@"hello", provide(), provide_cf()]; + // expected-warning@-1{{Call argument is unretained and unsafe}} + // expected-warning@-2{{Call argument is unretained and unsafe}} + [self doWork:@"hello", RetainPtr { provide() }.get(), RetainPtr { provide_cf() }.get()]; +} + +@end From 28c78ae8125a7b37318afe7f291bbbacd0d34630 Mon Sep 17 00:00:00 2001 From: Ryosuke Niwa Date: Wed, 12 Mar 2025 18:33:17 -0700 Subject: [PATCH 09/31] [alpha.webkit.UncountedCallArgsChecker] Treat an explicit construction of Ref from a Ref return value safe. (#130911) Fix a bug that an explicit construction of Ref out of a Ref return value would not be treated as safe. It is definitely safe albit redundant. --- clang/lib/StaticAnalyzer/Checkers/WebKit/ASTUtils.cpp | 2 ++ clang/test/Analysis/Checkers/WebKit/call-args.cpp | 11 +++++++++++ 2 files changed, 13 insertions(+) diff --git a/clang/lib/StaticAnalyzer/Checkers/WebKit/ASTUtils.cpp b/clang/lib/StaticAnalyzer/Checkers/WebKit/ASTUtils.cpp index ae0f8ad09f26e..c36c925c0d2d2 100644 --- a/clang/lib/StaticAnalyzer/Checkers/WebKit/ASTUtils.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/WebKit/ASTUtils.cpp @@ -84,6 +84,8 @@ bool tryToFindPtrOrigin( if (isCtorOfSafePtr(ConversionFunc)) return callback(E, true); } + if (isa(E) && isSafePtrType(cast->getType())) + return callback(E, true); } // FIXME: This can give false "origin" that would lead to false negatives // in checkers. See https://reviews.llvm.org/D37023 for reference. diff --git a/clang/test/Analysis/Checkers/WebKit/call-args.cpp b/clang/test/Analysis/Checkers/WebKit/call-args.cpp index e7afd9798da3e..d95ae9216edcf 100644 --- a/clang/test/Analysis/Checkers/WebKit/call-args.cpp +++ b/clang/test/Analysis/Checkers/WebKit/call-args.cpp @@ -407,6 +407,17 @@ namespace call_with_explicit_temporary_obj { void baz() { bar(); } + + class Foo { + Ref ensure(); + void foo() { + Ref { ensure() }->method(); + } + }; + + void baz(Ref&& arg) { + Ref { arg }->method(); + } } namespace call_with_explicit_construct { From 2fae8026fc061bbc44fc38fb87ff8c92fad0da0e Mon Sep 17 00:00:00 2001 From: Ryosuke Niwa Date: Wed, 12 Mar 2025 19:09:05 -0700 Subject: [PATCH 10/31] [alpha.webkit.webkit.RetainPtrCtorAdoptChecker] Add a new WebKit checker for correct use of RetainPtr, adoptNS, and adoptCF (#128679) Add a new WebKit checker to validate the correct use of RetainPtr constructor as well as adoptNS and adoptCF functions. adoptNS and adoptCf are used for +1 semantics and RetainPtr constructor is used for +0 semantics. --- clang/docs/analyzer/checkers.rst | 20 + .../clang/StaticAnalyzer/Checkers/Checkers.td | 4 + .../StaticAnalyzer/Checkers/CMakeLists.txt | 1 + .../Checkers/WebKit/PtrTypesSemantics.cpp | 7 +- .../Checkers/WebKit/PtrTypesSemantics.h | 3 +- .../WebKit/RetainPtrCtorAdoptChecker.cpp | 383 ++++++++++++++++++ .../Checkers/WebKit/objc-mock-types.h | 32 ++ .../WebKit/retain-ptr-ctor-adopt-use-arc.mm | 98 +++++ .../WebKit/retain-ptr-ctor-adopt-use.mm | 98 +++++ 9 files changed, 642 insertions(+), 4 deletions(-) create mode 100644 clang/lib/StaticAnalyzer/Checkers/WebKit/RetainPtrCtorAdoptChecker.cpp create mode 100644 clang/test/Analysis/Checkers/WebKit/retain-ptr-ctor-adopt-use-arc.mm create mode 100644 clang/test/Analysis/Checkers/WebKit/retain-ptr-ctor-adopt-use.mm diff --git a/clang/docs/analyzer/checkers.rst b/clang/docs/analyzer/checkers.rst index c96e0c6d9b78f..b494b2c650f7b 100644 --- a/clang/docs/analyzer/checkers.rst +++ b/clang/docs/analyzer/checkers.rst @@ -3736,6 +3736,26 @@ Here are some examples of situations that we warn about as they *might* be poten NSObject* unretained = retained.get(); // warn } +webkit.RetainPtrCtorAdoptChecker +"""""""""""""""""""""""""""""""" +The goal of this rule is to make sure the constructor of RetainPtr as well as adoptNS and adoptCF are used correctly. +When creating a RetainPtr with +1 semantics, adoptNS or adoptCF should be used, and in +0 semantics, RetainPtr constructor should be used. +Warn otherwise. + +These are examples of cases that we consider correct: + + .. code-block:: cpp + + RetainPtr ptr = adoptNS([[NSObject alloc] init]); // ok + RetainPtr ptr = CGImageGetColorSpace(image); // ok + +Here are some examples of cases that we consider incorrect use of RetainPtr constructor and adoptCF + + .. code-block:: cpp + + RetainPtr ptr = [[NSObject alloc] init]; // warn + auto ptr = adoptCF(CGImageGetColorSpace(image)); // warn + Debug Checkers --------------- diff --git a/clang/include/clang/StaticAnalyzer/Checkers/Checkers.td b/clang/include/clang/StaticAnalyzer/Checkers/Checkers.td index ebe95670275c3..10d3fd2ba0b5c 100644 --- a/clang/include/clang/StaticAnalyzer/Checkers/Checkers.td +++ b/clang/include/clang/StaticAnalyzer/Checkers/Checkers.td @@ -1827,4 +1827,8 @@ def UnretainedLocalVarsChecker : Checker<"UnretainedLocalVarsChecker">, HelpText<"Check unretained local variables.">, Documentation; +def RetainPtrCtorAdoptChecker : Checker<"RetainPtrCtorAdoptChecker">, + HelpText<"Check for correct use of RetainPtr constructor, adoptNS, and adoptCF">, + Documentation; + } // end alpha.webkit diff --git a/clang/lib/StaticAnalyzer/Checkers/CMakeLists.txt b/clang/lib/StaticAnalyzer/Checkers/CMakeLists.txt index d19623b3f2480..1ff3c345012a9 100644 --- a/clang/lib/StaticAnalyzer/Checkers/CMakeLists.txt +++ b/clang/lib/StaticAnalyzer/Checkers/CMakeLists.txt @@ -137,6 +137,7 @@ add_clang_library(clangStaticAnalyzerCheckers WebKit/MemoryUnsafeCastChecker.cpp WebKit/PtrTypesSemantics.cpp WebKit/RefCntblBaseVirtualDtorChecker.cpp + WebKit/RetainPtrCtorAdoptChecker.cpp WebKit/RawPtrRefCallArgsChecker.cpp WebKit/RawPtrRefLambdaCapturesChecker.cpp WebKit/RawPtrRefLocalVarsChecker.cpp diff --git a/clang/lib/StaticAnalyzer/Checkers/WebKit/PtrTypesSemantics.cpp b/clang/lib/StaticAnalyzer/Checkers/WebKit/PtrTypesSemantics.cpp index e780f610eb389..bfa58a11c6199 100644 --- a/clang/lib/StaticAnalyzer/Checkers/WebKit/PtrTypesSemantics.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/WebKit/PtrTypesSemantics.cpp @@ -226,15 +226,16 @@ void RetainTypeChecker::visitTypedef(const TypedefDecl *TD) { return; for (auto *Redecl : RT->getDecl()->getMostRecentDecl()->redecls()) { - if (Redecl->getAttr()) { + if (Redecl->getAttr() || + Redecl->getAttr()) { CFPointees.insert(RT); return; } } } -bool RetainTypeChecker::isUnretained(const QualType QT) { - if (ento::cocoa::isCocoaObjectRef(QT) && !IsARCEnabled) +bool RetainTypeChecker::isUnretained(const QualType QT, bool ignoreARC) { + if (ento::cocoa::isCocoaObjectRef(QT) && (!IsARCEnabled || ignoreARC)) return true; auto CanonicalType = QT.getCanonicalType(); auto PointeeType = CanonicalType->getPointeeType(); diff --git a/clang/lib/StaticAnalyzer/Checkers/WebKit/PtrTypesSemantics.h b/clang/lib/StaticAnalyzer/Checkers/WebKit/PtrTypesSemantics.h index 323d473665888..60bfd1a8dd480 100644 --- a/clang/lib/StaticAnalyzer/Checkers/WebKit/PtrTypesSemantics.h +++ b/clang/lib/StaticAnalyzer/Checkers/WebKit/PtrTypesSemantics.h @@ -75,7 +75,8 @@ class RetainTypeChecker { public: void visitTranslationUnitDecl(const TranslationUnitDecl *); void visitTypedef(const TypedefDecl *); - bool isUnretained(const QualType); + bool isUnretained(const QualType, bool ignoreARC = false); + bool isARCEnabled() const { return IsARCEnabled; } }; /// \returns true if \p Class is NS or CF objects AND not retained, false if diff --git a/clang/lib/StaticAnalyzer/Checkers/WebKit/RetainPtrCtorAdoptChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/WebKit/RetainPtrCtorAdoptChecker.cpp new file mode 100644 index 0000000000000..4ce3262e90e13 --- /dev/null +++ b/clang/lib/StaticAnalyzer/Checkers/WebKit/RetainPtrCtorAdoptChecker.cpp @@ -0,0 +1,383 @@ +//=======- RetainPtrCtorAdoptChecker.cpp -------------------------*- C++ -*-==// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#include "ASTUtils.h" +#include "DiagOutputUtils.h" +#include "PtrTypesSemantics.h" +#include "clang/AST/CXXInheritance.h" +#include "clang/AST/RecursiveASTVisitor.h" +#include "clang/AST/StmtVisitor.h" +#include "clang/Analysis/RetainSummaryManager.h" +#include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h" +#include "clang/StaticAnalyzer/Core/BugReporter/BugReporter.h" +#include "clang/StaticAnalyzer/Core/BugReporter/BugType.h" +#include "clang/StaticAnalyzer/Core/Checker.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h" +#include "llvm/ADT/DenseSet.h" +#include "llvm/ADT/SetVector.h" +#include + +using namespace clang; +using namespace ento; + +namespace { + +class RetainPtrCtorAdoptChecker + : public Checker> { +private: + BugType Bug; + mutable BugReporter *BR; + mutable std::unique_ptr Summaries; + mutable llvm::DenseSet CreateOrCopyOutArguments; + mutable RetainTypeChecker RTC; + +public: + RetainPtrCtorAdoptChecker() + : Bug(this, "Correct use of RetainPtr, adoptNS, and adoptCF", + "WebKit coding guidelines") {} + + void checkASTDecl(const TranslationUnitDecl *TUD, AnalysisManager &MGR, + BugReporter &BRArg) const { + BR = &BRArg; + + // The calls to checkAST* from AnalysisConsumer don't + // visit template instantiations or lambda classes. We + // want to visit those, so we make our own RecursiveASTVisitor. + struct LocalVisitor : public RecursiveASTVisitor { + const RetainPtrCtorAdoptChecker *Checker; + Decl *DeclWithIssue{nullptr}; + + using Base = RecursiveASTVisitor; + + explicit LocalVisitor(const RetainPtrCtorAdoptChecker *Checker) + : Checker(Checker) { + assert(Checker); + } + + bool shouldVisitTemplateInstantiations() const { return true; } + bool shouldVisitImplicitCode() const { return false; } + + bool TraverseDecl(Decl *D) { + llvm::SaveAndRestore SavedDecl(DeclWithIssue); + if (D && (isa(D) || isa(D))) + DeclWithIssue = D; + return Base::TraverseDecl(D); + } + + bool TraverseClassTemplateDecl(ClassTemplateDecl *CTD) { + if (safeGetName(CTD) == "RetainPtr") + return true; // Skip the contents of RetainPtr. + return Base::TraverseClassTemplateDecl(CTD); + } + + bool VisitTypedefDecl(TypedefDecl *TD) { + Checker->RTC.visitTypedef(TD); + return true; + } + + bool VisitCallExpr(const CallExpr *CE) { + Checker->visitCallExpr(CE, DeclWithIssue); + return true; + } + + bool VisitCXXConstructExpr(const CXXConstructExpr *CE) { + Checker->visitConstructExpr(CE, DeclWithIssue); + return true; + } + }; + + LocalVisitor visitor(this); + Summaries = std::make_unique( + TUD->getASTContext(), true /* trackObjCAndCFObjects */, + false /* trackOSObjects */); + RTC.visitTranslationUnitDecl(TUD); + visitor.TraverseDecl(const_cast(TUD)); + } + + bool isAdoptFn(const Decl *FnDecl) const { + auto Name = safeGetName(FnDecl); + return Name == "adoptNS" || Name == "adoptCF" || Name == "adoptNSArc" || + Name == "adoptCFArc"; + } + + bool isAdoptNS(const Decl *FnDecl) const { + auto Name = safeGetName(FnDecl); + return Name == "adoptNS" || Name == "adoptNSArc"; + } + + void visitCallExpr(const CallExpr *CE, const Decl *DeclWithIssue) const { + if (BR->getSourceManager().isInSystemHeader(CE->getExprLoc())) + return; + + auto *F = CE->getDirectCallee(); + if (!F) + return; + + if (!isAdoptFn(F) || !CE->getNumArgs()) { + rememberOutArguments(CE, F); + return; + } + + auto *Arg = CE->getArg(0)->IgnoreParenCasts(); + auto Result = isOwned(Arg); + auto Name = safeGetName(F); + if (Result == IsOwnedResult::Unknown) + Result = IsOwnedResult::NotOwned; + if (Result == IsOwnedResult::NotOwned && !isAllocInit(Arg) && + !isCreateOrCopy(Arg)) { + if (auto *DRE = dyn_cast(Arg)) { + if (CreateOrCopyOutArguments.contains(DRE->getDecl())) + return; + } + if (RTC.isARCEnabled() && isAdoptNS(F)) + reportUseAfterFree(Name, CE, DeclWithIssue, "when ARC is disabled"); + else + reportUseAfterFree(Name, CE, DeclWithIssue); + } + } + + void rememberOutArguments(const CallExpr *CE, + const FunctionDecl *Callee) const { + if (!isCreateOrCopyFunction(Callee)) + return; + + unsigned ArgCount = CE->getNumArgs(); + for (unsigned ArgIndex = 0; ArgIndex < ArgCount; ++ArgIndex) { + auto *Arg = CE->getArg(ArgIndex)->IgnoreParenCasts(); + auto *Unary = dyn_cast(Arg); + if (!Unary) + continue; + if (Unary->getOpcode() != UO_AddrOf) + continue; + auto *SubExpr = Unary->getSubExpr(); + if (!SubExpr) + continue; + auto *DRE = dyn_cast(SubExpr->IgnoreParenCasts()); + if (!DRE) + continue; + auto *Decl = DRE->getDecl(); + if (!Decl) + continue; + CreateOrCopyOutArguments.insert(Decl); + } + } + + void visitConstructExpr(const CXXConstructExpr *CE, + const Decl *DeclWithIssue) const { + if (BR->getSourceManager().isInSystemHeader(CE->getExprLoc())) + return; + + auto *Ctor = CE->getConstructor(); + if (!Ctor) + return; + + auto *Cls = Ctor->getParent(); + if (!Cls) + return; + + if (safeGetName(Cls) != "RetainPtr" || !CE->getNumArgs()) + return; + + // Ignore RetainPtr construction inside adoptNS, adoptCF, and retainPtr. + if (isAdoptFn(DeclWithIssue) || safeGetName(DeclWithIssue) == "retainPtr") + return; + + std::string Name = "RetainPtr constructor"; + auto *Arg = CE->getArg(0)->IgnoreParenCasts(); + auto Result = isOwned(Arg); + if (Result == IsOwnedResult::Unknown) + Result = IsOwnedResult::NotOwned; + if (Result == IsOwnedResult::Owned) + reportLeak(Name, CE, DeclWithIssue); + else if (RTC.isARCEnabled() && isAllocInit(Arg)) + reportLeak(Name, CE, DeclWithIssue, "when ARC is disabled"); + else if (isCreateOrCopy(Arg)) + reportLeak(Name, CE, DeclWithIssue); + } + + bool isAllocInit(const Expr *E) const { + auto *ObjCMsgExpr = dyn_cast(E); + if (!ObjCMsgExpr) + return false; + auto Selector = ObjCMsgExpr->getSelector(); + auto NameForFirstSlot = Selector.getNameForSlot(0); + if (NameForFirstSlot == "alloc" || NameForFirstSlot.starts_with("copy") || + NameForFirstSlot.starts_with("mutableCopy")) + return true; + if (!NameForFirstSlot.starts_with("init")) + return false; + if (!ObjCMsgExpr->isInstanceMessage()) + return false; + auto *Receiver = ObjCMsgExpr->getInstanceReceiver()->IgnoreParenCasts(); + if (!Receiver) + return false; + if (auto *InnerObjCMsgExpr = dyn_cast(Receiver)) { + auto InnerSelector = InnerObjCMsgExpr->getSelector(); + return InnerSelector.getNameForSlot(0) == "alloc"; + } else if (auto *CE = dyn_cast(Receiver)) { + if (auto *Callee = CE->getDirectCallee()) { + auto CalleeName = Callee->getName(); + return CalleeName.starts_with("alloc"); + } + } + return false; + } + + bool isCreateOrCopy(const Expr *E) const { + auto *CE = dyn_cast(E); + if (!CE) + return false; + auto *Callee = CE->getDirectCallee(); + if (!Callee) + return false; + return isCreateOrCopyFunction(Callee); + } + + bool isCreateOrCopyFunction(const FunctionDecl *FnDecl) const { + auto CalleeName = safeGetName(FnDecl); + return CalleeName.find("Create") != std::string::npos || + CalleeName.find("Copy") != std::string::npos; + } + + enum class IsOwnedResult { Unknown, Skip, Owned, NotOwned }; + IsOwnedResult isOwned(const Expr *E) const { + while (1) { + if (isa(E)) + return IsOwnedResult::NotOwned; + if (auto *DRE = dyn_cast(E)) { + auto QT = DRE->getType(); + if (isRetainPtrType(QT)) + return IsOwnedResult::NotOwned; + QT = QT.getCanonicalType(); + if (RTC.isUnretained(QT, true /* ignoreARC */)) + return IsOwnedResult::NotOwned; + auto *PointeeType = QT->getPointeeType().getTypePtrOrNull(); + if (PointeeType && PointeeType->isVoidType()) + return IsOwnedResult::NotOwned; // Assume reading void* as +0. + } + if (auto *TE = dyn_cast(E)) { + E = TE->getSubExpr(); + continue; + } + if (auto *ObjCMsgExpr = dyn_cast(E)) { + auto Summary = Summaries->getSummary(AnyCall(ObjCMsgExpr)); + auto RetEffect = Summary->getRetEffect(); + switch (RetEffect.getKind()) { + case RetEffect::NoRet: + return IsOwnedResult::Unknown; + case RetEffect::OwnedSymbol: + return IsOwnedResult::Owned; + case RetEffect::NotOwnedSymbol: + return IsOwnedResult::NotOwned; + case RetEffect::OwnedWhenTrackedReceiver: + if (auto *Receiver = ObjCMsgExpr->getInstanceReceiver()) { + E = Receiver->IgnoreParenCasts(); + continue; + } + return IsOwnedResult::Unknown; + case RetEffect::NoRetHard: + return IsOwnedResult::Unknown; + } + } + if (auto *CXXCE = dyn_cast(E)) { + if (auto *MD = CXXCE->getMethodDecl()) { + auto *Cls = MD->getParent(); + if (auto *CD = dyn_cast(MD)) { + auto QT = CD->getConversionType().getCanonicalType(); + auto *ResultType = QT.getTypePtrOrNull(); + if (safeGetName(Cls) == "RetainPtr" && ResultType && + (ResultType->isPointerType() || ResultType->isReferenceType() || + ResultType->isObjCObjectPointerType())) + return IsOwnedResult::NotOwned; + } + if (safeGetName(MD) == "leakRef" && safeGetName(Cls) == "RetainPtr") + return IsOwnedResult::Owned; + } + } + if (auto *CE = dyn_cast(E)) { + if (auto *Callee = CE->getDirectCallee()) { + if (isAdoptFn(Callee)) + return IsOwnedResult::NotOwned; + if (safeGetName(Callee) == "__builtin___CFStringMakeConstantString") + return IsOwnedResult::NotOwned; + auto RetType = Callee->getReturnType(); + if (isRetainPtrType(RetType)) + return IsOwnedResult::NotOwned; + } else if (auto *CalleeExpr = CE->getCallee()) { + if (isa(CalleeExpr)) + return IsOwnedResult::Skip; // Wait for instantiation. + } + auto Summary = Summaries->getSummary(AnyCall(CE)); + auto RetEffect = Summary->getRetEffect(); + switch (RetEffect.getKind()) { + case RetEffect::NoRet: + return IsOwnedResult::Unknown; + case RetEffect::OwnedSymbol: + return IsOwnedResult::Owned; + case RetEffect::NotOwnedSymbol: + return IsOwnedResult::NotOwned; + case RetEffect::OwnedWhenTrackedReceiver: + return IsOwnedResult::Unknown; + case RetEffect::NoRetHard: + return IsOwnedResult::Unknown; + } + } + break; + } + return IsOwnedResult::Unknown; + } + + void reportUseAfterFree(std::string &Name, const CallExpr *CE, + const Decl *DeclWithIssue, + const char *condition = nullptr) const { + SmallString<100> Buf; + llvm::raw_svector_ostream Os(Buf); + + Os << "Incorrect use of " << Name + << ". The argument is +0 and results in an use-after-free"; + if (condition) + Os << " " << condition; + Os << "."; + + PathDiagnosticLocation BSLoc(CE->getSourceRange().getBegin(), + BR->getSourceManager()); + auto Report = std::make_unique(Bug, Os.str(), BSLoc); + Report->addRange(CE->getSourceRange()); + Report->setDeclWithIssue(DeclWithIssue); + BR->emitReport(std::move(Report)); + } + + void reportLeak(std::string &Name, const CXXConstructExpr *CE, + const Decl *DeclWithIssue, + const char *condition = nullptr) const { + SmallString<100> Buf; + llvm::raw_svector_ostream Os(Buf); + + Os << "Incorrect use of " << Name + << ". The argument is +1 and results in a memory leak"; + if (condition) + Os << " " << condition; + Os << "."; + + PathDiagnosticLocation BSLoc(CE->getSourceRange().getBegin(), + BR->getSourceManager()); + auto Report = std::make_unique(Bug, Os.str(), BSLoc); + Report->addRange(CE->getSourceRange()); + Report->setDeclWithIssue(DeclWithIssue); + BR->emitReport(std::move(Report)); + } +}; +} // namespace + +void ento::registerRetainPtrCtorAdoptChecker(CheckerManager &Mgr) { + Mgr.registerChecker(); +} + +bool ento::shouldRegisterRetainPtrCtorAdoptChecker(const CheckerManager &mgr) { + return true; +} diff --git a/clang/test/Analysis/Checkers/WebKit/objc-mock-types.h b/clang/test/Analysis/Checkers/WebKit/objc-mock-types.h index 9b13810d0c5c9..c3925218c6c99 100644 --- a/clang/test/Analysis/Checkers/WebKit/objc-mock-types.h +++ b/clang/test/Analysis/Checkers/WebKit/objc-mock-types.h @@ -6,6 +6,7 @@ #define CF_BRIDGED_MUTABLE_TYPE(T) __attribute__((objc_bridge_mutable(T))) typedef CF_BRIDGED_TYPE(id) void * CFTypeRef; typedef signed char BOOL; +typedef unsigned char Boolean; typedef signed long CFIndex; typedef unsigned long NSUInteger; typedef const struct CF_BRIDGED_TYPE(id) __CFAllocator * CFAllocatorRef; @@ -13,12 +14,43 @@ typedef const struct CF_BRIDGED_TYPE(NSString) __CFString * CFStringRef; typedef const struct CF_BRIDGED_TYPE(NSArray) __CFArray * CFArrayRef; typedef struct CF_BRIDGED_MUTABLE_TYPE(NSMutableArray) __CFArray * CFMutableArrayRef; typedef struct CF_BRIDGED_MUTABLE_TYPE(CFRunLoopRef) __CFRunLoop * CFRunLoopRef; +#define NS_RETURNS_RETAINED __attribute__((ns_returns_retained)) +#define CF_CONSUMED __attribute__((cf_consumed)) +#define CF_RETURNS_RETAINED __attribute__((cf_returns_retained)) extern const CFAllocatorRef kCFAllocatorDefault; typedef struct _NSZone NSZone; CFMutableArrayRef CFArrayCreateMutable(CFAllocatorRef allocator, CFIndex capacity); extern void CFArrayAppendValue(CFMutableArrayRef theArray, const void *value); CFArrayRef CFArrayCreate(CFAllocatorRef allocator, const void **values, CFIndex numValues); CFIndex CFArrayGetCount(CFArrayRef theArray); + +typedef const struct CF_BRIDGED_TYPE(NSDictionary) __CFDictionary * CFDictionaryRef; +typedef struct CF_BRIDGED_MUTABLE_TYPE(NSMutableDictionary) __CFDictionary * CFMutableDictionaryRef; + +CFDictionaryRef CFDictionaryCreate(CFAllocatorRef allocator, const void **keys, const void **values, CFIndex numValues); +CFDictionaryRef CFDictionaryCreateCopy(CFAllocatorRef allocator, CFDictionaryRef theDict); +CFDictionaryRef CFDictionaryCreateMutableCopy(CFAllocatorRef allocator, CFIndex capacity, CFDictionaryRef theDict); +CFIndex CFDictionaryGetCount(CFDictionaryRef theDict); +Boolean CFDictionaryContainsKey(CFDictionaryRef theDict, const void *key); +Boolean CFDictionaryContainsValue(CFDictionaryRef theDict, const void *value); +const void *CFDictionaryGetValue(CFDictionaryRef theDict, const void *key); +void CFDictionaryAddValue(CFMutableDictionaryRef theDict, const void *key, const void *value); +void CFDictionarySetValue(CFMutableDictionaryRef theDict, const void *key, const void *value); +void CFDictionaryReplaceValue(CFMutableDictionaryRef theDict, const void *key, const void *value); +void CFDictionaryRemoveValue(CFMutableDictionaryRef theDict, const void *key); + +typedef struct CF_BRIDGED_TYPE(id) __SecTask * SecTaskRef; +SecTaskRef SecTaskCreateFromSelf(CFAllocatorRef allocator); + +typedef struct CF_BRIDGED_TYPE(id) CF_BRIDGED_MUTABLE_TYPE(IOSurface) __IOSurface * IOSurfaceRef; +IOSurfaceRef IOSurfaceCreate(CFDictionaryRef properties); + +typedef struct CF_BRIDGED_TYPE(id) __CVBuffer *CVBufferRef; +typedef CVBufferRef CVImageBufferRef; +typedef CVImageBufferRef CVPixelBufferRef; +typedef signed int CVReturn; +CVReturn CVPixelBufferCreateWithIOSurface(CFAllocatorRef allocator, IOSurfaceRef surface, CFDictionaryRef pixelBufferAttributes, CVPixelBufferRef * pixelBufferOut); + CFRunLoopRef CFRunLoopGetCurrent(void); CFRunLoopRef CFRunLoopGetMain(void); extern CFTypeRef CFRetain(CFTypeRef cf); diff --git a/clang/test/Analysis/Checkers/WebKit/retain-ptr-ctor-adopt-use-arc.mm b/clang/test/Analysis/Checkers/WebKit/retain-ptr-ctor-adopt-use-arc.mm new file mode 100644 index 0000000000000..99f488b578910 --- /dev/null +++ b/clang/test/Analysis/Checkers/WebKit/retain-ptr-ctor-adopt-use-arc.mm @@ -0,0 +1,98 @@ +// RUN: %clang_analyze_cc1 -analyzer-checker=alpha.webkit.RetainPtrCtorAdoptChecker -fobjc-arc -verify %s + +#include "objc-mock-types.h" + +void basic_correct() { + auto ns1 = adoptNS([SomeObj alloc]); + auto ns2 = adoptNS([[SomeObj alloc] init]); + RetainPtr ns3 = [ns1.get() next]; + auto ns4 = adoptNS([ns3 mutableCopy]); + auto ns5 = adoptNS([ns3 copyWithValue:3]); + auto ns6 = retainPtr([ns3 next]); + CFMutableArrayRef cf1 = adoptCF(CFArrayCreateMutable(kCFAllocatorDefault, 10)); + auto cf2 = adoptCF(SecTaskCreateFromSelf(kCFAllocatorDefault)); +} + +CFMutableArrayRef provide_cf(); + +void basic_wrong() { + RetainPtr ns1 = [[SomeObj alloc] init]; + // expected-warning@-1{{Incorrect use of RetainPtr constructor. The argument is +1 and results in a memory leak when ARC is disabled [alpha.webkit.RetainPtrCtorAdoptChecker]}} + auto ns2 = adoptNS([ns1.get() next]); + // expected-warning@-1{{Incorrect use of adoptNS. The argument is +0 and results in an use-after-free when ARC is disabled [alpha.webkit.RetainPtrCtorAdoptChecker]}} + RetainPtr cf1 = CFArrayCreateMutable(kCFAllocatorDefault, 10); + // expected-warning@-1{{Incorrect use of RetainPtr constructor. The argument is +1 and results in a memory leak [alpha.webkit.RetainPtrCtorAdoptChecker]}} + RetainPtr cf2 = adoptCF(provide_cf()); + // expected-warning@-1{{Incorrect use of adoptCF. The argument is +0 and results in an use-after-free [alpha.webkit.RetainPtrCtorAdoptChecker]}} + RetainPtr cf3 = SecTaskCreateFromSelf(kCFAllocatorDefault); + // expected-warning@-1{{Incorrect use of RetainPtr constructor. The argument is +1 and results in a memory leak [alpha.webkit.RetainPtrCtorAdoptChecker]}} +} + +RetainPtr cf_out_argument() { + auto surface = adoptCF(IOSurfaceCreate(nullptr)); + CVPixelBufferRef rawBuffer = nullptr; + auto status = CVPixelBufferCreateWithIOSurface(kCFAllocatorDefault, surface.get(), nullptr, &rawBuffer); + return adoptCF(rawBuffer); +} + +RetainPtr return_nullptr() { + return nullptr; +} + +RetainPtr return_retainptr() { + RetainPtr foo; + return foo; +} + +CFTypeRef CopyValueForSomething(); + +void cast_retainptr() { + RetainPtr foo; + RetainPtr bar = static_cast(foo); + + auto baz = adoptCF(CopyValueForSomething()).get(); + RetainPtr v = static_cast(baz); +} + +SomeObj* allocSomeObj(); + +void adopt_retainptr() { + RetainPtr foo = adoptNS([[SomeObj alloc] init]); + auto bar = adoptNS([allocSomeObj() init]); +} + +RetainPtr return_arg(CFArrayRef arg) { + return arg; +} + +class MemberInit { +public: + MemberInit(CFMutableArrayRef array, NSString *str, CFRunLoopRef runLoop) + : m_array(array) + , m_str(str) + , m_runLoop(runLoop) + { } + +private: + RetainPtr m_array; + RetainPtr m_str; + RetainPtr m_runLoop; +}; +void create_member_init() { + MemberInit init { CFArrayCreateMutable(kCFAllocatorDefault, 10), @"hello", CFRunLoopGetCurrent() }; +} + +RetainPtr cfstr() { + return CFSTR(""); +} + +template +static RetainPtr bridge_cast(RetainPtr&& ptr) +{ + return adoptNS((__bridge NSArray *)(ptr.leakRef())); +} + +RetainPtr create_cf_array(); +RetainPtr return_bridge_cast() { + return bridge_cast(create_cf_array()); +} diff --git a/clang/test/Analysis/Checkers/WebKit/retain-ptr-ctor-adopt-use.mm b/clang/test/Analysis/Checkers/WebKit/retain-ptr-ctor-adopt-use.mm new file mode 100644 index 0000000000000..111342bc0e388 --- /dev/null +++ b/clang/test/Analysis/Checkers/WebKit/retain-ptr-ctor-adopt-use.mm @@ -0,0 +1,98 @@ +// RUN: %clang_analyze_cc1 -analyzer-checker=alpha.webkit.RetainPtrCtorAdoptChecker -verify %s + +#include "objc-mock-types.h" + +void basic_correct() { + auto ns1 = adoptNS([SomeObj alloc]); + auto ns2 = adoptNS([[SomeObj alloc] init]); + RetainPtr ns3 = [ns1.get() next]; + auto ns4 = adoptNS([ns3 mutableCopy]); + auto ns5 = adoptNS([ns3 copyWithValue:3]); + auto ns6 = retainPtr([ns3 next]); + CFMutableArrayRef cf1 = adoptCF(CFArrayCreateMutable(kCFAllocatorDefault, 10)); + auto cf2 = adoptCF(SecTaskCreateFromSelf(kCFAllocatorDefault)); +} + +CFMutableArrayRef provide_cf(); + +void basic_wrong() { + RetainPtr ns1 = [[SomeObj alloc] init]; + // expected-warning@-1{{Incorrect use of RetainPtr constructor. The argument is +1 and results in a memory leak [alpha.webkit.RetainPtrCtorAdoptChecker]}} + auto ns2 = adoptNS([ns1.get() next]); + // expected-warning@-1{{Incorrect use of adoptNS. The argument is +0 and results in an use-after-free [alpha.webkit.RetainPtrCtorAdoptChecker]}} + RetainPtr cf1 = CFArrayCreateMutable(kCFAllocatorDefault, 10); + // expected-warning@-1{{Incorrect use of RetainPtr constructor. The argument is +1 and results in a memory leak [alpha.webkit.RetainPtrCtorAdoptChecker]}} + RetainPtr cf2 = adoptCF(provide_cf()); + // expected-warning@-1{{Incorrect use of adoptCF. The argument is +0 and results in an use-after-free [alpha.webkit.RetainPtrCtorAdoptChecker]}} + RetainPtr cf3 = SecTaskCreateFromSelf(kCFAllocatorDefault); + // expected-warning@-1{{Incorrect use of RetainPtr constructor. The argument is +1 and results in a memory leak [alpha.webkit.RetainPtrCtorAdoptChecker]}} +} + +RetainPtr cf_out_argument() { + auto surface = adoptCF(IOSurfaceCreate(nullptr)); + CVPixelBufferRef rawBuffer = nullptr; + auto status = CVPixelBufferCreateWithIOSurface(kCFAllocatorDefault, surface.get(), nullptr, &rawBuffer); + return adoptCF(rawBuffer); +} + +RetainPtr return_nullptr() { + return nullptr; +} + +RetainPtr return_retainptr() { + RetainPtr foo; + return foo; +} + +CFTypeRef CopyValueForSomething(); + +void cast_retainptr() { + RetainPtr foo; + RetainPtr bar = static_cast(foo); + + auto baz = adoptCF(CopyValueForSomething()).get(); + RetainPtr v = static_cast(baz); +} + +SomeObj* allocSomeObj(); + +void adopt_retainptr() { + RetainPtr foo = adoptNS([[SomeObj alloc] init]); + auto bar = adoptNS([allocSomeObj() init]); +} + +RetainPtr return_arg(CFArrayRef arg) { + return arg; +} + +class MemberInit { +public: + MemberInit(CFMutableArrayRef array, NSString *str, CFRunLoopRef runLoop) + : m_array(array) + , m_str(str) + , m_runLoop(runLoop) + { } + +private: + RetainPtr m_array; + RetainPtr m_str; + RetainPtr m_runLoop; +}; +void create_member_init() { + MemberInit init { CFArrayCreateMutable(kCFAllocatorDefault, 10), @"hello", CFRunLoopGetCurrent() }; +} + +RetainPtr cfstr() { + return CFSTR(""); +} + +template +static RetainPtr bridge_cast(RetainPtr&& ptr) +{ + return adoptNS((__bridge NSArray *)(ptr.leakRef())); +} + +RetainPtr create_cf_array(); +RetainPtr return_bridge_cast() { + return bridge_cast(create_cf_array()); +} From 63ddb4abf5ba3bd25eb5f13b48c18ee702e00b55 Mon Sep 17 00:00:00 2001 From: Ryosuke Niwa Date: Wed, 12 Mar 2025 20:21:22 -0700 Subject: [PATCH 11/31] [alpha.webkit.ForwardDeclChecker] Add a new WebKit checker for forward declarations (#130554) Add a new static analyzer which emits warnings for function call arguments, local variables, and member variables that are only forward declared. These forward declaration prevents other WebKit checkers from checking the safety of code. --- clang/docs/analyzer/checkers.rst | 18 + .../clang/StaticAnalyzer/Checkers/Checkers.td | 4 + .../StaticAnalyzer/Checkers/CMakeLists.txt | 1 + .../Checkers/WebKit/ForwardDeclChecker.cpp | 327 ++++++++++++++++++ .../Checkers/WebKit/forward-decl-checker.mm | 136 ++++++++ .../Checkers/WebKit/mock-system-header.h | 14 + .../Checkers/WebKit/objc-mock-types.h | 4 +- 7 files changed, 503 insertions(+), 1 deletion(-) create mode 100644 clang/lib/StaticAnalyzer/Checkers/WebKit/ForwardDeclChecker.cpp create mode 100644 clang/test/Analysis/Checkers/WebKit/forward-decl-checker.mm diff --git a/clang/docs/analyzer/checkers.rst b/clang/docs/analyzer/checkers.rst index b494b2c650f7b..5364dc150195d 100644 --- a/clang/docs/analyzer/checkers.rst +++ b/clang/docs/analyzer/checkers.rst @@ -3433,6 +3433,24 @@ Check for non-determinism caused by sorting of pointers. alpha.WebKit ^^^^^^^^^^^^ +alpha.webkit.ForwardDeclChecker +""""""""""""""""""""""""""""""" +Check for local variables, member variables, and function arguments that are forward declared. + +.. code-block:: cpp + + struct Obj; + Obj* provide(); + + struct Foo { + Obj* ptr; // warn + }; + + void foo() { + Obj* obj = provide(); // warn + consume(obj); // warn + } + .. _alpha-webkit-NoUncheckedPtrMemberChecker: alpha.webkit.MemoryUnsafeCastChecker diff --git a/clang/include/clang/StaticAnalyzer/Checkers/Checkers.td b/clang/include/clang/StaticAnalyzer/Checkers/Checkers.td index 10d3fd2ba0b5c..96dc11e2da0c2 100644 --- a/clang/include/clang/StaticAnalyzer/Checkers/Checkers.td +++ b/clang/include/clang/StaticAnalyzer/Checkers/Checkers.td @@ -1787,6 +1787,10 @@ def UncountedLambdaCapturesChecker : Checker<"UncountedLambdaCapturesChecker">, let ParentPackage = WebKitAlpha in { +def ForwardDeclChecker : Checker<"ForwardDeclChecker">, + HelpText<"Check for forward declared local or member variables and function arguments">, + Documentation; + def MemoryUnsafeCastChecker : Checker<"MemoryUnsafeCastChecker">, HelpText<"Check for memory unsafe casts from base type to derived type.">, Documentation; diff --git a/clang/lib/StaticAnalyzer/Checkers/CMakeLists.txt b/clang/lib/StaticAnalyzer/Checkers/CMakeLists.txt index 1ff3c345012a9..38f6c9e4f2c0e 100644 --- a/clang/lib/StaticAnalyzer/Checkers/CMakeLists.txt +++ b/clang/lib/StaticAnalyzer/Checkers/CMakeLists.txt @@ -134,6 +134,7 @@ add_clang_library(clangStaticAnalyzerCheckers ValistChecker.cpp VirtualCallChecker.cpp WebKit/ASTUtils.cpp + WebKit/ForwardDeclChecker.cpp WebKit/MemoryUnsafeCastChecker.cpp WebKit/PtrTypesSemantics.cpp WebKit/RefCntblBaseVirtualDtorChecker.cpp diff --git a/clang/lib/StaticAnalyzer/Checkers/WebKit/ForwardDeclChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/WebKit/ForwardDeclChecker.cpp new file mode 100644 index 0000000000000..291eb140d3202 --- /dev/null +++ b/clang/lib/StaticAnalyzer/Checkers/WebKit/ForwardDeclChecker.cpp @@ -0,0 +1,327 @@ +//=======- ForwardDeclChecker.cpp --------------------------------*- C++ -*-==// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#include "ASTUtils.h" +#include "DiagOutputUtils.h" +#include "PtrTypesSemantics.h" +#include "clang/AST/CXXInheritance.h" +#include "clang/AST/Decl.h" +#include "clang/AST/DeclCXX.h" +#include "clang/AST/RecursiveASTVisitor.h" +#include "clang/Analysis/DomainSpecific/CocoaConventions.h" +#include "clang/Basic/SourceLocation.h" +#include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h" +#include "clang/StaticAnalyzer/Core/BugReporter/BugReporter.h" +#include "clang/StaticAnalyzer/Core/BugReporter/BugType.h" +#include "clang/StaticAnalyzer/Core/Checker.h" +#include "llvm/ADT/DenseSet.h" +#include "llvm/Support/SaveAndRestore.h" +#include + +using namespace clang; +using namespace ento; + +namespace { + +class ForwardDeclChecker : public Checker> { + BugType Bug; + mutable BugReporter *BR; + mutable RetainTypeChecker RTC; + mutable llvm::DenseSet SystemTypes; + +public: + ForwardDeclChecker() + : Bug(this, "Forward declared member or local variable or parameter", + "WebKit coding guidelines") {} + + void checkASTDecl(const TranslationUnitDecl *TUD, AnalysisManager &MGR, + BugReporter &BRArg) const { + BR = &BRArg; + + // The calls to checkAST* from AnalysisConsumer don't + // visit template instantiations or lambda classes. We + // want to visit those, so we make our own RecursiveASTVisitor. + struct LocalVisitor : public RecursiveASTVisitor { + using Base = RecursiveASTVisitor; + + const ForwardDeclChecker *Checker; + Decl *DeclWithIssue{nullptr}; + + explicit LocalVisitor(const ForwardDeclChecker *Checker) + : Checker(Checker) { + assert(Checker); + } + + bool shouldVisitTemplateInstantiations() const { return true; } + bool shouldVisitImplicitCode() const { return false; } + + bool VisitTypedefDecl(TypedefDecl *TD) { + Checker->visitTypedef(TD); + return true; + } + + bool VisitRecordDecl(const RecordDecl *RD) { + Checker->visitRecordDecl(RD, DeclWithIssue); + return true; + } + + bool TraverseDecl(Decl *D) { + llvm::SaveAndRestore SavedDecl(DeclWithIssue); + if (D && (isa(D) || isa(D))) + DeclWithIssue = D; + return Base::TraverseDecl(D); + } + + bool VisitVarDecl(VarDecl *V) { + if (V->isLocalVarDecl()) + Checker->visitVarDecl(V, DeclWithIssue); + return true; + } + + bool VisitCallExpr(const CallExpr *CE) { + Checker->visitCallExpr(CE, DeclWithIssue); + return true; + } + + bool VisitCXXConstructExpr(const CXXConstructExpr *CE) { + Checker->visitConstructExpr(CE, DeclWithIssue); + return true; + } + + bool VisitObjCMessageExpr(const ObjCMessageExpr *ObjCMsgExpr) { + Checker->visitObjCMessageExpr(ObjCMsgExpr, DeclWithIssue); + return true; + } + }; + + LocalVisitor visitor(this); + RTC.visitTranslationUnitDecl(TUD); + visitor.TraverseDecl(const_cast(TUD)); + } + + void visitTypedef(const TypedefDecl *TD) const { + RTC.visitTypedef(TD); + auto QT = TD->getUnderlyingType().getCanonicalType(); + if (BR->getSourceManager().isInSystemHeader(TD->getBeginLoc())) { + if (auto *Type = QT.getTypePtrOrNull(); Type && QT->isPointerType()) + SystemTypes.insert(Type); + } + } + + bool isUnknownType(QualType QT) const { + auto *Type = QT.getTypePtrOrNull(); + if (!Type) + return false; + auto *CanonicalType = QT.getCanonicalType().getTypePtrOrNull(); + auto PointeeQT = Type->getPointeeType(); + auto *PointeeType = PointeeQT.getTypePtrOrNull(); + if (!PointeeType) + return false; + auto *R = PointeeType->getAsCXXRecordDecl(); + if (!R) // Forward declaration of a Objective-C interface is safe. + return false; + auto Name = R->getName(); + return !R->hasDefinition() && !RTC.isUnretained(QT) && + !SystemTypes.contains(CanonicalType) && + !Name.starts_with("Opaque") && Name != "_NSZone"; + } + + void visitRecordDecl(const RecordDecl *RD, const Decl *DeclWithIssue) const { + if (!RD->isThisDeclarationADefinition()) + return; + + if (RD->isImplicit() || RD->isLambda()) + return; + + const auto RDLocation = RD->getLocation(); + if (!RDLocation.isValid()) + return; + + const auto Kind = RD->getTagKind(); + if (Kind != TagTypeKind::Struct && Kind != TagTypeKind::Class) + return; + + if (BR->getSourceManager().isInSystemHeader(RDLocation)) + return; + + // Ref-counted smartpointers actually have raw-pointer to uncounted type as + // a member but we trust them to handle it correctly. + auto R = llvm::dyn_cast_or_null(RD); + if (!R || isRefCounted(R) || isCheckedPtr(R) || isRetainPtr(R)) + return; + + for (auto *Member : RD->fields()) { + auto QT = Member->getType(); + if (isUnknownType(QT)) { + SmallString<100> Buf; + llvm::raw_svector_ostream Os(Buf); + + const std::string TypeName = QT.getAsString(); + Os << "Member variable "; + printQuotedName(Os, Member); + Os << " uses a forward declared type '" << TypeName << "'"; + + const SourceLocation SrcLocToReport = Member->getBeginLoc(); + PathDiagnosticLocation BSLoc(SrcLocToReport, BR->getSourceManager()); + auto Report = std::make_unique(Bug, Os.str(), BSLoc); + Report->addRange(Member->getSourceRange()); + Report->setDeclWithIssue(DeclWithIssue); + BR->emitReport(std::move(Report)); + } + } + } + + void visitVarDecl(const VarDecl *V, const Decl *DeclWithIssue) const { + if (BR->getSourceManager().isInSystemHeader(V->getBeginLoc())) + return; + + auto QT = V->getType(); + if (!isUnknownType(QT)) + return; + + SmallString<100> Buf; + llvm::raw_svector_ostream Os(Buf); + Os << "Local variable "; + printQuotedQualifiedName(Os, V); + + reportBug(V->getBeginLoc(), V->getSourceRange(), DeclWithIssue, Os.str(), + QT); + } + + void visitCallExpr(const CallExpr *CE, const Decl *DeclWithIssue) const { + if (BR->getSourceManager().isInSystemHeader(CE->getExprLoc())) + return; + + if (auto *F = CE->getDirectCallee()) { + // Skip the first argument for overloaded member operators (e. g. lambda + // or std::function call operator). + unsigned ArgIdx = + isa(CE) && isa_and_nonnull(F); + + for (auto P = F->param_begin(); + P < F->param_end() && ArgIdx < CE->getNumArgs(); ++P, ++ArgIdx) + visitCallArg(CE->getArg(ArgIdx), *P, DeclWithIssue); + } + } + + void visitConstructExpr(const CXXConstructExpr *CE, + const Decl *DeclWithIssue) const { + if (BR->getSourceManager().isInSystemHeader(CE->getExprLoc())) + return; + + if (auto *F = CE->getConstructor()) { + // Skip the first argument for overloaded member operators (e. g. lambda + // or std::function call operator). + unsigned ArgIdx = + isa(CE) && isa_and_nonnull(F); + + for (auto P = F->param_begin(); + P < F->param_end() && ArgIdx < CE->getNumArgs(); ++P, ++ArgIdx) + visitCallArg(CE->getArg(ArgIdx), *P, DeclWithIssue); + } + } + + void visitObjCMessageExpr(const ObjCMessageExpr *E, + const Decl *DeclWithIssue) const { + if (BR->getSourceManager().isInSystemHeader(E->getExprLoc())) + return; + + if (auto *Receiver = E->getInstanceReceiver()->IgnoreParenCasts()) { + if (isUnknownType(E->getReceiverType())) + reportUnknownRecieverType(Receiver, DeclWithIssue); + } + + auto *MethodDecl = E->getMethodDecl(); + if (!MethodDecl) + return; + + auto ArgCount = E->getNumArgs(); + for (unsigned i = 0; i < ArgCount && i < MethodDecl->param_size(); ++i) + visitCallArg(E->getArg(i), MethodDecl->getParamDecl(i), DeclWithIssue); + } + + void visitCallArg(const Expr *Arg, const ParmVarDecl *Param, + const Decl *DeclWithIssue) const { + auto *ArgExpr = Arg->IgnoreParenCasts(); + if (auto *InnerCE = dyn_cast(Arg)) { + auto *InnerCallee = InnerCE->getDirectCallee(); + if (InnerCallee && InnerCallee->isInStdNamespace() && + safeGetName(InnerCallee) == "move" && InnerCE->getNumArgs() == 1) { + ArgExpr = InnerCE->getArg(0); + if (ArgExpr) + ArgExpr = ArgExpr->IgnoreParenCasts(); + } + } + if (isa(ArgExpr) || isa(ArgExpr) || + isa(ArgExpr)) + return; + if (auto *DRE = dyn_cast(ArgExpr)) { + if (auto *ValDecl = DRE->getDecl()) { + if (isa(ValDecl)) + return; + } + } + + QualType ArgType = Param->getType(); + if (!isUnknownType(ArgType)) + return; + + reportUnknownArgType(Arg, Param, DeclWithIssue); + } + + void reportUnknownArgType(const Expr *CA, const ParmVarDecl *Param, + const Decl *DeclWithIssue) const { + assert(CA); + + SmallString<100> Buf; + llvm::raw_svector_ostream Os(Buf); + + const std::string paramName = safeGetName(Param); + Os << "Call argument"; + if (!paramName.empty()) { + Os << " for parameter "; + printQuotedQualifiedName(Os, Param); + } + + reportBug(CA->getExprLoc(), CA->getSourceRange(), DeclWithIssue, Os.str(), + Param->getType()); + } + + void reportUnknownRecieverType(const Expr *Receiver, + const Decl *DeclWithIssue) const { + assert(Receiver); + reportBug(Receiver->getExprLoc(), Receiver->getSourceRange(), DeclWithIssue, + "Receiver", Receiver->getType()); + } + + void reportBug(const SourceLocation &SrcLoc, const SourceRange &SrcRange, + const Decl *DeclWithIssue, const StringRef &Description, + QualType Type) const { + SmallString<100> Buf; + llvm::raw_svector_ostream Os(Buf); + + const std::string TypeName = Type.getAsString(); + Os << Description << " uses a forward declared type '" << TypeName << "'"; + + PathDiagnosticLocation BSLoc(SrcLoc, BR->getSourceManager()); + auto Report = std::make_unique(Bug, Os.str(), BSLoc); + Report->addRange(SrcRange); + Report->setDeclWithIssue(DeclWithIssue); + BR->emitReport(std::move(Report)); + } +}; + +} // namespace + +void ento::registerForwardDeclChecker(CheckerManager &Mgr) { + Mgr.registerChecker(); +} + +bool ento::shouldRegisterForwardDeclChecker(const CheckerManager &) { + return true; +} diff --git a/clang/test/Analysis/Checkers/WebKit/forward-decl-checker.mm b/clang/test/Analysis/Checkers/WebKit/forward-decl-checker.mm new file mode 100644 index 0000000000000..151cbe2affa92 --- /dev/null +++ b/clang/test/Analysis/Checkers/WebKit/forward-decl-checker.mm @@ -0,0 +1,136 @@ +// RUN: %clang_analyze_cc1 -analyzer-checker=alpha.webkit.ForwardDeclChecker -verify %s + +#include "mock-types.h" +#include "objc-mock-types.h" +#include "mock-system-header.h" + +namespace std { + +template struct remove_reference { + typedef T type; +}; + +template struct remove_reference { + typedef T type; +}; + +template typename remove_reference::type&& move(T&& t); + +} // namespace std + +typedef struct OpaqueJSString * JSStringRef; + +class Obj; +@class ObjCObj; + +Obj* provide_obj_ptr(); +void receive_obj_ptr(Obj* p = nullptr); + +Obj* ptr(Obj* arg) { + receive_obj_ptr(provide_obj_ptr()); + // expected-warning@-1{{Call argument for parameter 'p' uses a forward declared type 'Obj *'}} + auto *obj = provide_obj_ptr(); + // expected-warning@-1{{Local variable 'obj' uses a forward declared type 'Obj *'}} + receive_obj_ptr(arg); + receive_obj_ptr(nullptr); + receive_obj_ptr(); + return obj; +} + +Obj& provide_obj_ref(); +void receive_obj_ref(Obj& p); + +Obj& ref() { + receive_obj_ref(provide_obj_ref()); + // expected-warning@-1{{Call argument for parameter 'p' uses a forward declared type 'Obj &'}} + auto &obj = provide_obj_ref(); + // expected-warning@-1{{Local variable 'obj' uses a forward declared type 'Obj &'}} + return obj; +} + +Obj&& provide_obj_rval(); +void receive_obj_rval(Obj&& p); + +void rval(Obj&& arg) { + receive_obj_rval(provide_obj_rval()); + // expected-warning@-1{{Call argument for parameter 'p' uses a forward declared type 'Obj &&'}} + auto &&obj = provide_obj_rval(); + // expected-warning@-1{{Local variable 'obj' uses a forward declared type 'Obj &&'}} + receive_obj_rval(std::move(arg)); +} + +ObjCObj *provide_objcobj(); +void receive_objcobj(ObjCObj *p); +ObjCObj *objc_ptr() { + receive_objcobj(provide_objcobj()); + auto *objcobj = provide_objcobj(); + return objcobj; +} + +struct WrapperObj { + Obj* ptr { nullptr }; + // expected-warning@-1{{Member variable 'ptr' uses a forward declared type 'Obj *'}} + + WrapperObj(Obj* obj); + WrapperObj(Obj& obj); + WrapperObj(Obj&& obj); +}; + +void construct_ptr(Obj&& arg) { + WrapperObj wrapper1(provide_obj_ptr()); + // expected-warning@-1{{Call argument for parameter 'obj' uses a forward declared type 'Obj *'}} + WrapperObj wrapper2(provide_obj_ref()); + // expected-warning@-1{{Call argument for parameter 'obj' uses a forward declared type 'Obj &'}} + WrapperObj wrapper3(std::move(arg)); +} + +JSStringRef provide_opaque_ptr(); +void receive_opaque_ptr(JSStringRef); +NSZone *provide_zone(); + +JSStringRef opaque_ptr() { + receive_opaque_ptr(provide_opaque_ptr()); + auto ref = provide_opaque_ptr(); + return ref; +} + +@interface AnotherObj : NSObject +- (Obj *)ptr; +- (Obj &)ref; +- (void)objc; +- (void)doMoreWork:(ObjCObj *)obj; +@end + +@implementation AnotherObj +- (Obj *)ptr { + receive_obj_ptr(provide_obj_ptr()); + // expected-warning@-1{{Call argument for parameter 'p' uses a forward declared type 'Obj *'}} + auto *obj = provide_obj_ptr(); + // expected-warning@-1{{Local variable 'obj' uses a forward declared type 'Obj *'}} + return obj; +} + +- (Obj &)ref { + receive_obj_ref(provide_obj_ref()); + // expected-warning@-1{{Call argument for parameter 'p' uses a forward declared type 'Obj &'}} + auto &obj = provide_obj_ref(); + // expected-warning@-1{{Local variable 'obj' uses a forward declared type 'Obj &'}} + return obj; +} + +- (void)objc { + auto *obj = provide_objcobj(); + [obj doWork]; + [self doMoreWork:provide_objcobj()]; + [self doMoreWork:nil]; +} + +- (void)doMoreWork:(ObjCObj *)obj { + auto array = CFArrayCreateMutable(kCFAllocatorDefault, 10); + CFArrayAppendValue(array, nullptr); + auto log = os_log_create("Foo", "Bar"); + os_log_msg(log, OS_LOG_TYPE_DEFAULT, "Some Log"); + auto *zone = provide_zone(); +} + +@end diff --git a/clang/test/Analysis/Checkers/WebKit/mock-system-header.h b/clang/test/Analysis/Checkers/WebKit/mock-system-header.h index a1d30957b19cb..73d6e3dbf4643 100644 --- a/clang/test/Analysis/Checkers/WebKit/mock-system-header.h +++ b/clang/test/Analysis/Checkers/WebKit/mock-system-header.h @@ -15,3 +15,17 @@ template struct MemberVariable { T* obj { nullptr }; }; + +typedef unsigned char uint8_t; + +enum os_log_type_t : uint8_t { + OS_LOG_TYPE_DEFAULT = 0x00, + OS_LOG_TYPE_INFO = 0x01, + OS_LOG_TYPE_DEBUG = 0x02, + OS_LOG_TYPE_ERROR = 0x10, + OS_LOG_TYPE_FAULT = 0x11, +}; + +typedef struct os_log_s *os_log_t; +os_log_t os_log_create(const char *subsystem, const char *category); +void os_log_msg(os_log_t oslog, os_log_type_t type, const char *msg); diff --git a/clang/test/Analysis/Checkers/WebKit/objc-mock-types.h b/clang/test/Analysis/Checkers/WebKit/objc-mock-types.h index c3925218c6c99..5bd265596a0b4 100644 --- a/clang/test/Analysis/Checkers/WebKit/objc-mock-types.h +++ b/clang/test/Analysis/Checkers/WebKit/objc-mock-types.h @@ -14,9 +14,11 @@ typedef const struct CF_BRIDGED_TYPE(NSString) __CFString * CFStringRef; typedef const struct CF_BRIDGED_TYPE(NSArray) __CFArray * CFArrayRef; typedef struct CF_BRIDGED_MUTABLE_TYPE(NSMutableArray) __CFArray * CFMutableArrayRef; typedef struct CF_BRIDGED_MUTABLE_TYPE(CFRunLoopRef) __CFRunLoop * CFRunLoopRef; + #define NS_RETURNS_RETAINED __attribute__((ns_returns_retained)) #define CF_CONSUMED __attribute__((cf_consumed)) #define CF_RETURNS_RETAINED __attribute__((cf_returns_retained)) + extern const CFAllocatorRef kCFAllocatorDefault; typedef struct _NSZone NSZone; CFMutableArrayRef CFArrayCreateMutable(CFAllocatorRef allocator, CFIndex capacity); @@ -278,4 +280,4 @@ using WTF::adoptNS; using WTF::adoptCF; using WTF::retainPtr; using WTF::downcast; -using WTF::bridge_cast; \ No newline at end of file +using WTF::bridge_cast; From fd04eee2b6dea0ec328c2f56f648710982c3f277 Mon Sep 17 00:00:00 2001 From: Ryosuke Niwa Date: Mon, 17 Mar 2025 23:47:10 -0700 Subject: [PATCH 12/31] [alpha.webkit.UncountedCallArgsChecker] os_log functions should be treated as safe. (#131500) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit …os_log functions should be treated as safe in call arguments checkers. Also treat __builtin_* functions and __libcpp_verbose_abort functions as "trivial" for the purpose in call argument checkers. --- .../Checkers/WebKit/PtrTypesSemantics.cpp | 11 +++++++++-- .../Checkers/WebKit/PtrTypesSemantics.h | 3 +++ .../Checkers/WebKit/RawPtrRefCallArgsChecker.cpp | 3 +++ .../Analysis/Checkers/WebKit/mock-system-header.h | 2 +- .../Analysis/Checkers/WebKit/uncounted-obj-arg.cpp | 6 +++++- .../Analysis/Checkers/WebKit/uncounted-obj-arg.mm | 4 ++++ 6 files changed, 25 insertions(+), 4 deletions(-) diff --git a/clang/lib/StaticAnalyzer/Checkers/WebKit/PtrTypesSemantics.cpp b/clang/lib/StaticAnalyzer/Checkers/WebKit/PtrTypesSemantics.cpp index bfa58a11c6199..b4d2353a03cd2 100644 --- a/clang/lib/StaticAnalyzer/Checkers/WebKit/PtrTypesSemantics.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/WebKit/PtrTypesSemantics.cpp @@ -424,6 +424,14 @@ bool isPtrConversion(const FunctionDecl *F) { return false; } +bool isTrivialBuiltinFunction(const FunctionDecl *F) { + if (!F || !F->getDeclName().isIdentifier()) + return false; + auto Name = F->getName(); + return Name.starts_with("__builtin") || Name == "__libcpp_verbose_abort" || + Name.starts_with("os_log") || Name.starts_with("_os_log"); +} + bool isSingleton(const FunctionDecl *F) { assert(F); // FIXME: check # of params == 1 @@ -601,8 +609,7 @@ class TrivialFunctionAnalysisVisitor Name == "isMainThreadOrGCThread" || Name == "isMainRunLoop" || Name == "isWebThread" || Name == "isUIThread" || Name == "mayBeGCThread" || Name == "compilerFenceForCrash" || - Name == "bitwise_cast" || Name.find("__builtin") == 0 || - Name == "__libcpp_verbose_abort") + Name == "bitwise_cast" || isTrivialBuiltinFunction(Callee)) return true; return IsFunctionTrivial(Callee); diff --git a/clang/lib/StaticAnalyzer/Checkers/WebKit/PtrTypesSemantics.h b/clang/lib/StaticAnalyzer/Checkers/WebKit/PtrTypesSemantics.h index 60bfd1a8dd480..096675fb912f2 100644 --- a/clang/lib/StaticAnalyzer/Checkers/WebKit/PtrTypesSemantics.h +++ b/clang/lib/StaticAnalyzer/Checkers/WebKit/PtrTypesSemantics.h @@ -142,6 +142,9 @@ std::optional isGetterOfSafePtr(const clang::CXXMethodDecl *Method); /// pointer types. bool isPtrConversion(const FunctionDecl *F); +/// \returns true if \p F is a builtin function which is considered trivial. +bool isTrivialBuiltinFunction(const FunctionDecl *F); + /// \returns true if \p F is a static singleton function. bool isSingleton(const FunctionDecl *F); diff --git a/clang/lib/StaticAnalyzer/Checkers/WebKit/RawPtrRefCallArgsChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/WebKit/RawPtrRefCallArgsChecker.cpp index b825be2dea8b1..95d2a7175255a 100644 --- a/clang/lib/StaticAnalyzer/Checkers/WebKit/RawPtrRefCallArgsChecker.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/WebKit/RawPtrRefCallArgsChecker.cpp @@ -249,6 +249,9 @@ class RawPtrRefCallArgsChecker if (Callee && TFA.isTrivial(Callee) && !Callee->isVirtualAsWritten()) return true; + if (isTrivialBuiltinFunction(Callee)) + return true; + if (CE->getNumArgs() == 0) return false; diff --git a/clang/test/Analysis/Checkers/WebKit/mock-system-header.h b/clang/test/Analysis/Checkers/WebKit/mock-system-header.h index 73d6e3dbf4643..e993fd697ffab 100644 --- a/clang/test/Analysis/Checkers/WebKit/mock-system-header.h +++ b/clang/test/Analysis/Checkers/WebKit/mock-system-header.h @@ -28,4 +28,4 @@ enum os_log_type_t : uint8_t { typedef struct os_log_s *os_log_t; os_log_t os_log_create(const char *subsystem, const char *category); -void os_log_msg(os_log_t oslog, os_log_type_t type, const char *msg); +void os_log_msg(os_log_t oslog, os_log_type_t type, const char *msg, ...); diff --git a/clang/test/Analysis/Checkers/WebKit/uncounted-obj-arg.cpp b/clang/test/Analysis/Checkers/WebKit/uncounted-obj-arg.cpp index 0279e2c68ec6d..69842264af56b 100644 --- a/clang/test/Analysis/Checkers/WebKit/uncounted-obj-arg.cpp +++ b/clang/test/Analysis/Checkers/WebKit/uncounted-obj-arg.cpp @@ -695,9 +695,13 @@ RefPtr object(); void someFunction(const RefCounted&); void test2() { - someFunction(*object()); + someFunction(*object()); } void system_header() { callMethod(object); } + +void log(RefCountable* obj) { + os_log_msg(os_log_create("WebKit", "DOM"), OS_LOG_TYPE_INFO, "obj: %p next: %p", obj, obj->next()); +} \ No newline at end of file diff --git a/clang/test/Analysis/Checkers/WebKit/uncounted-obj-arg.mm b/clang/test/Analysis/Checkers/WebKit/uncounted-obj-arg.mm index 08319016023e3..b78a67610df3c 100644 --- a/clang/test/Analysis/Checkers/WebKit/uncounted-obj-arg.mm +++ b/clang/test/Analysis/Checkers/WebKit/uncounted-obj-arg.mm @@ -50,3 +50,7 @@ @interface WrapperObj : NSObject static void foo(WrapperObj *configuration) { configuration._protectedWebExtensionControllerConfiguration->copy(); } + +void log(RefCountable* obj) { + os_log_msg(os_log_create("WebKit", "DOM"), OS_LOG_TYPE_INFO, "obj: %p next: %p", obj, obj->next()); +} From 2a385be66b5684c122713152f6b45bd067f314b9 Mon Sep 17 00:00:00 2001 From: Ryosuke Niwa Date: Tue, 18 Mar 2025 15:43:43 -0700 Subject: [PATCH 13/31] [webkit.NoUncountedMemberChecker] Fix a regression that every class is treated as if it's ref countable. (#131249) This PR fixes a regression that webkit.NoUncountedMemberChecker and alpha.webkit.NoUncheckedMemberChecker emits warnings for every class as if they supported ref counting and checked ptr because we were erroneously coercing the return value of isRefCountable and isCheckedPtrCapable, which is std::optional, to boolean values. --- .../Checkers/WebKit/RawPtrRefMemberChecker.cpp | 4 ++-- .../Analysis/Checkers/WebKit/uncounted-members.cpp | 10 +++++++++- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/clang/lib/StaticAnalyzer/Checkers/WebKit/RawPtrRefMemberChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/WebKit/RawPtrRefMemberChecker.cpp index d6ab6f4efbf29..d5ab547892d3b 100644 --- a/clang/lib/StaticAnalyzer/Checkers/WebKit/RawPtrRefMemberChecker.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/WebKit/RawPtrRefMemberChecker.cpp @@ -224,7 +224,7 @@ class NoUncountedMemberChecker final : public RawPtrRefMemberChecker { std::optional isPtrCompatible(const clang::QualType, const clang::CXXRecordDecl *R) const final { - return R && isRefCountable(R); + return R ? isRefCountable(R) : std::nullopt; } bool isPtrCls(const clang::CXXRecordDecl *R) const final { @@ -247,7 +247,7 @@ class NoUncheckedPtrMemberChecker final : public RawPtrRefMemberChecker { std::optional isPtrCompatible(const clang::QualType, const clang::CXXRecordDecl *R) const final { - return R && isCheckedPtrCapable(R); + return R ? isCheckedPtrCapable(R) : std::nullopt; } bool isPtrCls(const clang::CXXRecordDecl *R) const final { diff --git a/clang/test/Analysis/Checkers/WebKit/uncounted-members.cpp b/clang/test/Analysis/Checkers/WebKit/uncounted-members.cpp index bca7b3bad3a15..1bdbaedefbfeb 100644 --- a/clang/test/Analysis/Checkers/WebKit/uncounted-members.cpp +++ b/clang/test/Analysis/Checkers/WebKit/uncounted-members.cpp @@ -36,7 +36,6 @@ namespace members { }; } - namespace ignore_unions { union Foo { RefCountable* a; @@ -60,3 +59,12 @@ void foo(RefCountable* t) { } } // ignore_system_header + +namespace ignore_non_ref_countable { + struct Foo { + }; + + struct Bar { + Foo* foo; + }; +} \ No newline at end of file From 50ad6fb5aec53b7c4f3b0b43f538c43703c8757e Mon Sep 17 00:00:00 2001 From: Ryosuke Niwa Date: Wed, 26 Mar 2025 16:57:49 -0700 Subject: [PATCH 14/31] [webkit.RefCntblBaseVirtualDtor] Add support for NoVirtualDestructorBase. (#132497) This PR adds the support for WTF::NoVirtualDestructorBase, which signifies to the checker that the class is exempt from having a virtual destructor. --- .../WebKit/RefCntblBaseVirtualDtorChecker.cpp | 10 +++++++++- .../WebKit/ref-cntbl-base-virtual-dtor.cpp | 17 +++++++++++++++++ 2 files changed, 26 insertions(+), 1 deletion(-) diff --git a/clang/lib/StaticAnalyzer/Checkers/WebKit/RefCntblBaseVirtualDtorChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/WebKit/RefCntblBaseVirtualDtorChecker.cpp index e80246f49a310..961e54f83ad9a 100644 --- a/clang/lib/StaticAnalyzer/Checkers/WebKit/RefCntblBaseVirtualDtorChecker.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/WebKit/RefCntblBaseVirtualDtorChecker.cpp @@ -203,6 +203,13 @@ class RefCntblBaseVirtualDtorChecker if (!C) continue; + bool isExempt = T.getAsString() == "NoVirtualDestructorBase" && + safeGetName(C->getParent()) == "WTF"; + if (isExempt || ExemptDecls.contains(C)) { + ExemptDecls.insert(RD); + continue; + } + if (auto *CTSD = dyn_cast(C)) { for (auto &Arg : CTSD->getTemplateArgs().asArray()) { if (Arg.getKind() != TemplateArgument::Type) @@ -224,12 +231,13 @@ class RefCntblBaseVirtualDtorChecker llvm::SetVector Decls; llvm::DenseSet CRTPs; + llvm::DenseSet ExemptDecls; }; LocalVisitor visitor(this); visitor.TraverseDecl(const_cast(TUD)); for (auto *RD : visitor.Decls) { - if (visitor.CRTPs.contains(RD)) + if (visitor.CRTPs.contains(RD) || visitor.ExemptDecls.contains(RD)) continue; visitCXXRecordDecl(RD); } diff --git a/clang/test/Analysis/Checkers/WebKit/ref-cntbl-base-virtual-dtor.cpp b/clang/test/Analysis/Checkers/WebKit/ref-cntbl-base-virtual-dtor.cpp index 5cf7e7614d06e..fd4144d572e01 100644 --- a/clang/test/Analysis/Checkers/WebKit/ref-cntbl-base-virtual-dtor.cpp +++ b/clang/test/Analysis/Checkers/WebKit/ref-cntbl-base-virtual-dtor.cpp @@ -1,5 +1,13 @@ // RUN: %clang_analyze_cc1 -analyzer-checker=webkit.RefCntblBaseVirtualDtor -verify %s +namespace WTF { + +class NoVirtualDestructorBase { }; + +}; + +using WTF::NoVirtualDestructorBase; + struct RefCntblBase { void ref() {} void deref() {} @@ -19,6 +27,15 @@ struct [[clang::suppress]] SuppressedDerivedWithVirtualDtor : RefCntblBase { virtual ~SuppressedDerivedWithVirtualDtor() {} }; +class ClassWithoutVirtualDestructor : public NoVirtualDestructorBase { +public: + void ref() const; + void deref() const; +}; + +class DerivedClassWithoutVirtualDestructor : public ClassWithoutVirtualDestructor { +}; + // FIXME: Support attributes on base specifiers? Currently clang // doesn't support such attributes at all, even though it knows // how to parse them. From 41ffb479812b3ca720a0e958524a8bb36bb686bf Mon Sep 17 00:00:00 2001 From: Ryosuke Niwa Date: Thu, 27 Mar 2025 15:47:38 -0700 Subject: [PATCH 15/31] [WebKit Checkers] Recognize Objective-C and CF pointer conversion functions. (#132784) Recognize dynamic_objc_cast, checked_objc_cast, dynamic_cf_cast, and checked_cf_cast. --- .../Checkers/WebKit/PtrTypesSemantics.cpp | 5 +- .../WebKit/RawPtrRefCallArgsChecker.cpp | 7 +- .../Checkers/WebKit/objc-mock-types.h | 158 ++++++++++++++++++ .../Checkers/WebKit/unretained-call-args.mm | 27 +++ .../Checkers/WebKit/unretained-local-vars.mm | 27 +++ 5 files changed, 218 insertions(+), 6 deletions(-) diff --git a/clang/lib/StaticAnalyzer/Checkers/WebKit/PtrTypesSemantics.cpp b/clang/lib/StaticAnalyzer/Checkers/WebKit/PtrTypesSemantics.cpp index b4d2353a03cd2..7bc04ee565d03 100644 --- a/clang/lib/StaticAnalyzer/Checkers/WebKit/PtrTypesSemantics.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/WebKit/PtrTypesSemantics.cpp @@ -418,7 +418,10 @@ bool isPtrConversion(const FunctionDecl *F) { FunctionName == "dynamicDowncast" || FunctionName == "downcast" || FunctionName == "checkedDowncast" || FunctionName == "uncheckedDowncast" || FunctionName == "bitwise_cast" || - FunctionName == "bridge_cast") + FunctionName == "bridge_cast" || FunctionName == "bridge_id_cast" || + FunctionName == "dynamic_cf_cast" || FunctionName == "checked_cf_cast" || + FunctionName == "dynamic_objc_cast" || + FunctionName == "checked_objc_cast") return true; return false; diff --git a/clang/lib/StaticAnalyzer/Checkers/WebKit/RawPtrRefCallArgsChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/WebKit/RawPtrRefCallArgsChecker.cpp index 95d2a7175255a..507a91f9c7eca 100644 --- a/clang/lib/StaticAnalyzer/Checkers/WebKit/RawPtrRefCallArgsChecker.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/WebKit/RawPtrRefCallArgsChecker.cpp @@ -289,15 +289,12 @@ class RawPtrRefCallArgsChecker overloadedOperatorType == OO_PipePipe) return true; - if (isCtorOfSafePtr(Callee)) + if (isCtorOfSafePtr(Callee) || isPtrConversion(Callee)) return true; auto name = safeGetName(Callee); if (name == "adoptRef" || name == "getPtr" || name == "WeakPtr" || - name == "dynamicDowncast" || name == "downcast" || - name == "checkedDowncast" || name == "uncheckedDowncast" || - name == "bitwise_cast" || name == "is" || name == "equal" || - name == "hash" || name == "isType" || + name == "is" || name == "equal" || name == "hash" || name == "isType" || // FIXME: Most/all of these should be implemented via attributes. name == "equalIgnoringASCIICase" || name == "equalIgnoringASCIICaseCommon" || diff --git a/clang/test/Analysis/Checkers/WebKit/objc-mock-types.h b/clang/test/Analysis/Checkers/WebKit/objc-mock-types.h index 5bd265596a0b4..ef46a7c0a2925 100644 --- a/clang/test/Analysis/Checkers/WebKit/objc-mock-types.h +++ b/clang/test/Analysis/Checkers/WebKit/objc-mock-types.h @@ -5,6 +5,7 @@ #define CF_BRIDGED_TYPE(T) __attribute__((objc_bridge(T))) #define CF_BRIDGED_MUTABLE_TYPE(T) __attribute__((objc_bridge_mutable(T))) typedef CF_BRIDGED_TYPE(id) void * CFTypeRef; +typedef unsigned long long CFTypeID; typedef signed char BOOL; typedef unsigned char Boolean; typedef signed long CFIndex; @@ -21,6 +22,8 @@ typedef struct CF_BRIDGED_MUTABLE_TYPE(CFRunLoopRef) __CFRunLoop * CFRunLoopRef; extern const CFAllocatorRef kCFAllocatorDefault; typedef struct _NSZone NSZone; +CFTypeID CFGetTypeID(CFTypeRef cf); +CFTypeID CFArrayGetTypeID(); CFMutableArrayRef CFArrayCreateMutable(CFAllocatorRef allocator, CFIndex capacity); extern void CFArrayAppendValue(CFMutableArrayRef theArray, const void *value); CFArrayRef CFArrayCreate(CFAllocatorRef allocator, const void **values, CFIndex numValues); @@ -29,6 +32,7 @@ CFIndex CFArrayGetCount(CFArrayRef theArray); typedef const struct CF_BRIDGED_TYPE(NSDictionary) __CFDictionary * CFDictionaryRef; typedef struct CF_BRIDGED_MUTABLE_TYPE(NSMutableDictionary) __CFDictionary * CFMutableDictionaryRef; +CFTypeID CFDictionaryGetTypeID(); CFDictionaryRef CFDictionaryCreate(CFAllocatorRef allocator, const void **keys, const void **values, CFIndex numValues); CFDictionaryRef CFDictionaryCreateCopy(CFAllocatorRef allocator, CFDictionaryRef theDict); CFDictionaryRef CFDictionaryCreateMutableCopy(CFAllocatorRef allocator, CFIndex capacity, CFDictionaryRef theDict); @@ -135,6 +139,8 @@ __attribute__((objc_root_class)) namespace WTF { +void WTFCrash(void); + template class RetainPtr; template RetainPtr adoptNS(T*); template RetainPtr adoptCF(T); @@ -273,11 +279,163 @@ inline CFTypeRef bridge_cast(NSObject *object) return (__bridge CFTypeRef)object; } +inline id bridge_id_cast(CFTypeRef object) +{ + return (__bridge id)object; +} + +inline RetainPtr bridge_id_cast(RetainPtr&& object) +{ +#if __has_feature(objc_arc) + return adoptNS((__bridge_transfer id)object.leakRef()); +#else + return adoptNS((__bridge id)object.leakRef()); +#endif +} + +template +struct ObjCTypeCastTraits { +public: + static bool isType(id object) { return [object isKindOfClass:[ExpectedType class]]; } + + template + static bool isType(const ArgType *object) { return [object isKindOfClass:[ExpectedType class]]; } +}; + +template +inline bool is_objc(ArgType * source) +{ + return source && ObjCTypeCastTraits::isType(source); +} + +template inline T *checked_objc_cast(id object) +{ + if (!object) + return nullptr; + + if (!is_objc(object)) + WTFCrash(); + + return reinterpret_cast(object); +} + +template inline T *checked_objc_cast(U *object) +{ + if (!object) + return nullptr; + + if (!is_objc(object)) + WTFCrash(); + + return static_cast(object); +} + +template RetainPtr dynamic_objc_cast(RetainPtr&& object) +{ + if (!is_objc(object.get())) + return nullptr; + return adoptNS(static_cast(object.leakRef())); +} + +template RetainPtr dynamic_objc_cast(RetainPtr&& object) +{ + if (!is_objc(object.get())) + return nullptr; + return adoptNS(reinterpret_cast(object.leakRef())); +} + +template RetainPtr dynamic_objc_cast(const RetainPtr& object) +{ + if (!is_objc(object.get())) + return nullptr; + return static_cast(object.get()); } +template RetainPtr dynamic_objc_cast(const RetainPtr& object) +{ + if (!is_objc(object.get())) + return nullptr; + return reinterpret_cast(object.get()); +} + +template T *dynamic_objc_cast(NSObject *object) +{ + if (!is_objc(object)) + return nullptr; + return static_cast(object); +} + +template T *dynamic_objc_cast(id object) +{ + if (!is_objc(object)) + return nullptr; + return reinterpret_cast(object); +} + +template struct CFTypeTrait; + +template T dynamic_cf_cast(CFTypeRef object) +{ + if (!object) + return nullptr; + + if (CFGetTypeID(object) != CFTypeTrait::typeID()) + return nullptr; + + return static_cast(const_cast(object)); +} + +template T checked_cf_cast(CFTypeRef object) +{ + if (!object) + return nullptr; + + if (CFGetTypeID(object) != CFTypeTrait::typeID()) + WTFCrash(); + + return static_cast(const_cast(object)); +} + +template RetainPtr dynamic_cf_cast(RetainPtr&& object) +{ + if (!object) + return nullptr; + + if (CFGetTypeID(object.get()) != CFTypeTrait::typeID()) + return nullptr; + + return adoptCF(static_cast(const_cast(object.leakRef()))); +} + +} // namespace WTF + +#define WTF_DECLARE_CF_TYPE_TRAIT(ClassName) \ +template <> \ +struct WTF::CFTypeTrait { \ + static inline CFTypeID typeID(void) { return ClassName##GetTypeID(); } \ +}; + +WTF_DECLARE_CF_TYPE_TRAIT(CFArray); +WTF_DECLARE_CF_TYPE_TRAIT(CFDictionary); + +#define WTF_DECLARE_CF_MUTABLE_TYPE_TRAIT(ClassName, MutableClassName) \ +template <> \ +struct WTF::CFTypeTrait { \ + static inline CFTypeID typeID(void) { return ClassName##GetTypeID(); } \ +}; + +WTF_DECLARE_CF_MUTABLE_TYPE_TRAIT(CFArray, CFMutableArray); +WTF_DECLARE_CF_MUTABLE_TYPE_TRAIT(CFDictionary, CFMutableDictionary); + using WTF::RetainPtr; using WTF::adoptNS; using WTF::adoptCF; using WTF::retainPtr; using WTF::downcast; using WTF::bridge_cast; +using WTF::bridge_id_cast; +using WTF::is_objc; +using WTF::checked_objc_cast; +using WTF::dynamic_objc_cast; +using WTF::checked_cf_cast; +using WTF::dynamic_cf_cast; \ No newline at end of file diff --git a/clang/test/Analysis/Checkers/WebKit/unretained-call-args.mm b/clang/test/Analysis/Checkers/WebKit/unretained-call-args.mm index 3411cbdf5aa9b..5fb362dc3dd98 100644 --- a/clang/test/Analysis/Checkers/WebKit/unretained-call-args.mm +++ b/clang/test/Analysis/Checkers/WebKit/unretained-call-args.mm @@ -374,6 +374,33 @@ bool baz(NSObject *obj) { } } +namespace ptr_conversion { + +SomeObj *provide_obj(); + +void dobjc(SomeObj* obj) { + [dynamic_objc_cast(obj) doMoreWork:nil]; +} + +void cobjc(SomeObj* obj) { + [checked_objc_cast(obj) doMoreWork:nil]; +} + +unsigned dcf(CFTypeRef obj) { + return CFArrayGetCount(dynamic_cf_cast(obj)); +} + +unsigned ccf(CFTypeRef obj) { + return CFArrayGetCount(checked_cf_cast(obj)); +} + +void some_function(id); +void idcf(CFTypeRef obj) { + some_function(bridge_id_cast(obj)); +} + +} // ptr_conversion + @interface TestObject : NSObject - (void)doWork:(NSString *)msg, ...; - (void)doWorkOnSelf; diff --git a/clang/test/Analysis/Checkers/WebKit/unretained-local-vars.mm b/clang/test/Analysis/Checkers/WebKit/unretained-local-vars.mm index 21ef6a5dca519..0a3d9e54fa024 100644 --- a/clang/test/Analysis/Checkers/WebKit/unretained-local-vars.mm +++ b/clang/test/Analysis/Checkers/WebKit/unretained-local-vars.mm @@ -359,6 +359,33 @@ void bar() { } } +namespace ptr_conversion { + +SomeObj *provide_obj(); + +void dobjc(SomeObj* obj) { + if (auto *otherObj = dynamic_objc_cast(obj)) + [otherObj doMoreWork:nil]; +} + +void cobjc(SomeObj* obj) { + auto *otherObj = checked_objc_cast(obj); + [otherObj doMoreWork:nil]; +} + +unsigned dcf(CFTypeRef obj) { + if (CFArrayRef array = dynamic_cf_cast(obj)) + return CFArrayGetCount(array); + return 0; +} + +unsigned ccf(CFTypeRef obj) { + CFArrayRef array = checked_cf_cast(obj); + return CFArrayGetCount(array); +} + +} // ptr_conversion + bool doMoreWorkOpaque(OtherObj*); @implementation OtherObj From 1069648e97a49a125693058dbab47f6ccffba88a Mon Sep 17 00:00:00 2001 From: Ryosuke Niwa Date: Thu, 27 Mar 2025 16:45:47 -0700 Subject: [PATCH 16/31] Fix the assertion failure in Analysis/Checkers/WebKit/forward-decl-checker.mm after https://github.com/llvm/llvm-project/pull/132784. (#133341) --- .../lib/StaticAnalyzer/Checkers/WebKit/ForwardDeclChecker.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/clang/lib/StaticAnalyzer/Checkers/WebKit/ForwardDeclChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/WebKit/ForwardDeclChecker.cpp index 291eb140d3202..a524593b0119b 100644 --- a/clang/lib/StaticAnalyzer/Checkers/WebKit/ForwardDeclChecker.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/WebKit/ForwardDeclChecker.cpp @@ -231,7 +231,8 @@ class ForwardDeclChecker : public Checker> { if (BR->getSourceManager().isInSystemHeader(E->getExprLoc())) return; - if (auto *Receiver = E->getInstanceReceiver()->IgnoreParenCasts()) { + if (auto *Receiver = E->getInstanceReceiver()) { + Receiver = Receiver->IgnoreParenCasts(); if (isUnknownType(E->getReceiverType())) reportUnknownRecieverType(Receiver, DeclWithIssue); } From dc2a68162070f485d09568bcf01125270af26344 Mon Sep 17 00:00:00 2001 From: Ryosuke Niwa Date: Fri, 28 Mar 2025 17:34:51 -0700 Subject: [PATCH 17/31] [alpha.webkit.RawPtrRefMemberChecker] The checker doesn't warn Objective-C types in ivars. (#132833) This PR fixes the bug that we weren't generating warnings when a raw poiner is used to point to a NS type in Objective-C ivars. Also fix the bug that we weren't suppressing this warning in system headers. --- .../WebKit/RawPtrRefMemberChecker.cpp | 46 +++++++++++++++++-- .../Checkers/WebKit/mock-system-header.h | 11 +++++ .../Checkers/WebKit/unretained-members.mm | 11 +++++ 3 files changed, 65 insertions(+), 3 deletions(-) diff --git a/clang/lib/StaticAnalyzer/Checkers/WebKit/RawPtrRefMemberChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/WebKit/RawPtrRefMemberChecker.cpp index d5ab547892d3b..70dc288a40dab 100644 --- a/clang/lib/StaticAnalyzer/Checkers/WebKit/RawPtrRefMemberChecker.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/WebKit/RawPtrRefMemberChecker.cpp @@ -112,6 +112,14 @@ class RawPtrRefMemberChecker } void visitObjCDecl(const ObjCContainerDecl *CD) const { + if (BR->getSourceManager().isInSystemHeader(CD->getLocation())) + return; + + ObjCContainerDecl::PropertyMap map; + CD->collectPropertiesToImplement(map); + for (auto it : map) + visitObjCPropertyDecl(CD, it.second); + if (auto *ID = dyn_cast(CD)) { for (auto *Ivar : ID->ivars()) visitIvarDecl(CD, Ivar); @@ -134,6 +142,35 @@ class RawPtrRefMemberChecker std::optional IsCompatible = isPtrCompatible(QT, IvarCXXRD); if (IsCompatible && *IsCompatible) reportBug(Ivar, IvarType, IvarCXXRD, CD); + } else { + std::optional IsCompatible = isPtrCompatible(QT, nullptr); + auto *PointeeType = IvarType->getPointeeType().getTypePtrOrNull(); + if (IsCompatible && *IsCompatible) { + auto *Desugared = PointeeType->getUnqualifiedDesugaredType(); + if (auto *ObjCType = dyn_cast_or_null(Desugared)) + reportBug(Ivar, IvarType, ObjCType->getDecl(), CD); + } + } + } + + void visitObjCPropertyDecl(const ObjCContainerDecl *CD, + const ObjCPropertyDecl *PD) const { + auto QT = PD->getType(); + const Type *PropType = QT.getTypePtrOrNull(); + if (!PropType) + return; + if (auto *PropCXXRD = PropType->getPointeeCXXRecordDecl()) { + std::optional IsCompatible = isPtrCompatible(QT, PropCXXRD); + if (IsCompatible && *IsCompatible) + reportBug(PD, PropType, PropCXXRD, CD); + } else { + std::optional IsCompatible = isPtrCompatible(QT, nullptr); + auto *PointeeType = PropType->getPointeeType().getTypePtrOrNull(); + if (IsCompatible && *IsCompatible) { + auto *Desugared = PointeeType->getUnqualifiedDesugaredType(); + if (auto *ObjCType = dyn_cast_or_null(Desugared)) + reportBug(PD, PropType, ObjCType->getDecl(), CD); + } } } @@ -182,9 +219,12 @@ class RawPtrRefMemberChecker SmallString<100> Buf; llvm::raw_svector_ostream Os(Buf); - if (isa(ClassCXXRD)) - Os << "Instance variable "; - else + if (isa(ClassCXXRD)) { + if (isa(Member)) + Os << "Property "; + else + Os << "Instance variable "; + } else Os << "Member variable "; printQuotedName(Os, Member); Os << " in "; diff --git a/clang/test/Analysis/Checkers/WebKit/mock-system-header.h b/clang/test/Analysis/Checkers/WebKit/mock-system-header.h index e993fd697ffab..450fb24687343 100644 --- a/clang/test/Analysis/Checkers/WebKit/mock-system-header.h +++ b/clang/test/Analysis/Checkers/WebKit/mock-system-header.h @@ -29,3 +29,14 @@ enum os_log_type_t : uint8_t { typedef struct os_log_s *os_log_t; os_log_t os_log_create(const char *subsystem, const char *category); void os_log_msg(os_log_t oslog, os_log_type_t type, const char *msg, ...); + +typedef const struct __attribute__((objc_bridge(NSString))) __CFString * CFStringRef; + +#ifdef __OBJC__ +@class NSString; +@interface SystemObject { + NSString *ns_string; + CFStringRef cf_string; +} +@end +#endif diff --git a/clang/test/Analysis/Checkers/WebKit/unretained-members.mm b/clang/test/Analysis/Checkers/WebKit/unretained-members.mm index e068a583c18c5..92d70a94427c0 100644 --- a/clang/test/Analysis/Checkers/WebKit/unretained-members.mm +++ b/clang/test/Analysis/Checkers/WebKit/unretained-members.mm @@ -1,6 +1,7 @@ // RUN: %clang_analyze_cc1 -analyzer-checker=alpha.webkit.NoUnretainedMemberChecker -verify %s #include "objc-mock-types.h" +#include "mock-system-header.h" namespace members { @@ -58,3 +59,13 @@ void forceTmplToInstantiate(FooTmpl) {} void forceTmplToInstantiate(RefPtr) {} } + +@interface AnotherObject : NSObject { + NSString *ns_string; + // expected-warning@-1{{Instance variable 'ns_string' in 'AnotherObject' is a raw pointer to retainable type 'NSString'; member variables must be a RetainPtr}} + CFStringRef cf_string; + // expected-warning@-1{{Instance variable 'cf_string' in 'AnotherObject' is a retainable type 'CFStringRef'; member variables must be a RetainPtr}} +} +@property(nonatomic, strong) NSString *prop_string; +// expected-warning@-1{{Property 'prop_string' in 'AnotherObject' is a raw pointer to retainable type 'NSString'; member variables must be a RetainPtr}} +@end From ea79d254d8c95ae86494c537332a8ef843c87a54 Mon Sep 17 00:00:00 2001 From: Ryosuke Niwa Date: Mon, 31 Mar 2025 14:59:41 -0700 Subject: [PATCH 18/31] [alpha.webkit.NoUnretainedMemberChecker] Ignore system-header-defined ivar / property of a forward declared type (#133755) Prior to this PR, we were emitting warnings for Objective-C ivars and properties if the forward declaration of the type appeared first in a non-system header. This PR fixes the checker so tha we'd ignore ivars and properties defined for a forward declared type. --- .../StaticAnalyzer/Checkers/WebKit/RawPtrRefMemberChecker.cpp | 4 ++++ clang/test/Analysis/Checkers/WebKit/unretained-members.mm | 2 ++ 2 files changed, 6 insertions(+) diff --git a/clang/lib/StaticAnalyzer/Checkers/WebKit/RawPtrRefMemberChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/WebKit/RawPtrRefMemberChecker.cpp index 70dc288a40dab..4dd2bf578da6b 100644 --- a/clang/lib/StaticAnalyzer/Checkers/WebKit/RawPtrRefMemberChecker.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/WebKit/RawPtrRefMemberChecker.cpp @@ -134,6 +134,8 @@ class RawPtrRefMemberChecker void visitIvarDecl(const ObjCContainerDecl *CD, const ObjCIvarDecl *Ivar) const { + if (BR->getSourceManager().isInSystemHeader(Ivar->getLocation())) + return; auto QT = Ivar->getType(); const Type *IvarType = QT.getTypePtrOrNull(); if (!IvarType) @@ -155,6 +157,8 @@ class RawPtrRefMemberChecker void visitObjCPropertyDecl(const ObjCContainerDecl *CD, const ObjCPropertyDecl *PD) const { + if (BR->getSourceManager().isInSystemHeader(PD->getLocation())) + return; auto QT = PD->getType(); const Type *PropType = QT.getTypePtrOrNull(); if (!PropType) diff --git a/clang/test/Analysis/Checkers/WebKit/unretained-members.mm b/clang/test/Analysis/Checkers/WebKit/unretained-members.mm index 92d70a94427c0..fff1f8ede091b 100644 --- a/clang/test/Analysis/Checkers/WebKit/unretained-members.mm +++ b/clang/test/Analysis/Checkers/WebKit/unretained-members.mm @@ -1,5 +1,7 @@ // RUN: %clang_analyze_cc1 -analyzer-checker=alpha.webkit.NoUnretainedMemberChecker -verify %s +@class SystemObject; + #include "objc-mock-types.h" #include "mock-system-header.h" From fd3261179f975fc257d422c976889657f7cfc67a Mon Sep 17 00:00:00 2001 From: Ryosuke Niwa Date: Fri, 4 Apr 2025 11:25:24 -0700 Subject: [PATCH 19/31] [WebKit checkers] Treat Objective-C message send return value as safe (#133605) Objective-C selectors are supposed to return autoreleased object. Treat these return values as safe. --- .../Checkers/WebKit/RawPtrRefCallArgsChecker.cpp | 8 ++++++++ .../Checkers/WebKit/RawPtrRefLocalVarsChecker.cpp | 8 ++++++++ .../Analysis/Checkers/WebKit/objc-mock-types.h | 5 +++++ .../Checkers/WebKit/unretained-call-args-arc.mm | 9 +++++++++ .../Checkers/WebKit/unretained-call-args.mm | 9 +++++++++ .../Checkers/WebKit/unretained-local-vars.mm | 15 +++++++++++++-- 6 files changed, 52 insertions(+), 2 deletions(-) diff --git a/clang/lib/StaticAnalyzer/Checkers/WebKit/RawPtrRefCallArgsChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/WebKit/RawPtrRefCallArgsChecker.cpp index 507a91f9c7eca..b1cfa2afe1227 100644 --- a/clang/lib/StaticAnalyzer/Checkers/WebKit/RawPtrRefCallArgsChecker.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/WebKit/RawPtrRefCallArgsChecker.cpp @@ -47,6 +47,7 @@ class RawPtrRefCallArgsChecker virtual std::optional isUnsafePtr(QualType) const = 0; virtual bool isSafePtr(const CXXRecordDecl *Record) const = 0; virtual bool isSafePtrType(const QualType type) const = 0; + virtual bool isSafeExpr(const Expr *) const { return false; } virtual const char *ptrKind() const = 0; void checkASTDecl(const TranslationUnitDecl *TUD, AnalysisManager &MGR, @@ -236,6 +237,8 @@ class RawPtrRefCallArgsChecker return true; if (EFA.isACallToEnsureFn(ArgOrigin)) return true; + if (isSafeExpr(ArgOrigin)) + return true; return false; }); } @@ -472,6 +475,11 @@ class UnretainedCallArgsChecker final : public RawPtrRefCallArgsChecker { return isRetainPtrType(type); } + bool isSafeExpr(const Expr *E) const final { + return ento::cocoa::isCocoaObjectRef(E->getType()) && + isa(E); + } + const char *ptrKind() const final { return "unretained"; } }; diff --git a/clang/lib/StaticAnalyzer/Checkers/WebKit/RawPtrRefLocalVarsChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/WebKit/RawPtrRefLocalVarsChecker.cpp index 37eb6631d57d8..d0a5c53a13e82 100644 --- a/clang/lib/StaticAnalyzer/Checkers/WebKit/RawPtrRefLocalVarsChecker.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/WebKit/RawPtrRefLocalVarsChecker.cpp @@ -182,6 +182,7 @@ class RawPtrRefLocalVarsChecker virtual std::optional isUnsafePtr(const QualType T) const = 0; virtual bool isSafePtr(const CXXRecordDecl *) const = 0; virtual bool isSafePtrType(const QualType) const = 0; + virtual bool isSafeExpr(const Expr *) const { return false; } virtual const char *ptrKind() const = 0; void checkASTDecl(const TranslationUnitDecl *TUD, AnalysisManager &MGR, @@ -306,6 +307,9 @@ class RawPtrRefLocalVarsChecker if (EFA.isACallToEnsureFn(InitArgOrigin)) return true; + if (isSafeExpr(InitArgOrigin)) + return true; + if (auto *Ref = llvm::dyn_cast(InitArgOrigin)) { if (auto *MaybeGuardian = dyn_cast_or_null(Ref->getFoundDecl())) { @@ -432,6 +436,10 @@ class UnretainedLocalVarsChecker final : public RawPtrRefLocalVarsChecker { bool isSafePtrType(const QualType type) const final { return isRetainPtrType(type); } + bool isSafeExpr(const Expr *E) const final { + return ento::cocoa::isCocoaObjectRef(E->getType()) && + isa(E); + } const char *ptrKind() const final { return "unretained"; } }; diff --git a/clang/test/Analysis/Checkers/WebKit/objc-mock-types.h b/clang/test/Analysis/Checkers/WebKit/objc-mock-types.h index ef46a7c0a2925..059a203e3b0d1 100644 --- a/clang/test/Analysis/Checkers/WebKit/objc-mock-types.h +++ b/clang/test/Analysis/Checkers/WebKit/objc-mock-types.h @@ -71,6 +71,7 @@ __attribute__((objc_root_class)) + (Class) superclass; - (instancetype) init; - (instancetype)retain; +- (instancetype)autorelease; - (void)release; - (BOOL)isKindOfClass:(Class)aClass; @end @@ -221,6 +222,10 @@ template struct RetainPtr { operator PtrType() const { return t; } operator bool() const { return t; } +#if !__has_feature(objc_arc) + PtrType autorelease() { [[clang::suppress]] return [t autorelease]; } +#endif + private: CFTypeRef toCFTypeRef(id ptr) { return (__bridge CFTypeRef)ptr; } CFTypeRef toCFTypeRef(const void* ptr) { return (CFTypeRef)ptr; } diff --git a/clang/test/Analysis/Checkers/WebKit/unretained-call-args-arc.mm b/clang/test/Analysis/Checkers/WebKit/unretained-call-args-arc.mm index eb4735da60a05..f1f4d912663aa 100644 --- a/clang/test/Analysis/Checkers/WebKit/unretained-call-args-arc.mm +++ b/clang/test/Analysis/Checkers/WebKit/unretained-call-args-arc.mm @@ -18,6 +18,7 @@ void foo() { @interface AnotherObj : NSObject - (void)foo:(SomeObj *)obj; +- (SomeObj *)getSomeObj; @end @implementation AnotherObj @@ -27,4 +28,12 @@ - (void)foo:(SomeObj*)obj { CFArrayAppendValue(provide_cf(), nullptr); // expected-warning@-1{{Call argument for parameter 'theArray' is unretained and unsafe [alpha.webkit.UnretainedCallArgsChecker]}} } + +- (SomeObj *)getSomeObj { + return provide(); +} + +- (void)doWorkOnSomeObj { + [[self getSomeObj] doWork]; +} @end diff --git a/clang/test/Analysis/Checkers/WebKit/unretained-call-args.mm b/clang/test/Analysis/Checkers/WebKit/unretained-call-args.mm index 5fb362dc3dd98..1a93904079444 100644 --- a/clang/test/Analysis/Checkers/WebKit/unretained-call-args.mm +++ b/clang/test/Analysis/Checkers/WebKit/unretained-call-args.mm @@ -404,6 +404,7 @@ void idcf(CFTypeRef obj) { @interface TestObject : NSObject - (void)doWork:(NSString *)msg, ...; - (void)doWorkOnSelf; +- (SomeObj *)getSomeObj; @end @implementation TestObject @@ -420,4 +421,12 @@ - (void)doWorkOnSelf { [self doWork:@"hello", RetainPtr { provide() }.get(), RetainPtr { provide_cf() }.get()]; } +- (SomeObj *)getSomeObj { + return RetainPtr(provide()).autorelease(); +} + +- (void)doWorkOnSomeObj { + [[self getSomeObj] doWork]; +} + @end diff --git a/clang/test/Analysis/Checkers/WebKit/unretained-local-vars.mm b/clang/test/Analysis/Checkers/WebKit/unretained-local-vars.mm index 0a3d9e54fa024..a71a80ea3d647 100644 --- a/clang/test/Analysis/Checkers/WebKit/unretained-local-vars.mm +++ b/clang/test/Analysis/Checkers/WebKit/unretained-local-vars.mm @@ -14,8 +14,9 @@ void bar(SomeObj *) {} } // namespace raw_ptr namespace pointer { +SomeObj *provide(); void foo_ref() { - SomeObj *bar = [[SomeObj alloc] init]; + SomeObj *bar = provide(); // expected-warning@-1{{Local variable 'bar' is unretained and unsafe [alpha.webkit.UnretainedLocalVarsChecker]}} [bar doWork]; } @@ -387,6 +388,7 @@ unsigned ccf(CFTypeRef obj) { } // ptr_conversion bool doMoreWorkOpaque(OtherObj*); +SomeObj* provide(); @implementation OtherObj - (instancetype)init { @@ -397,4 +399,13 @@ - (instancetype)init { - (void)doMoreWork:(OtherObj *)other { doMoreWorkOpaque(other); } -@end \ No newline at end of file + +- (SomeObj*)getSomeObj { + return RetainPtr(provide()).autorelease(); +} + +- (void)storeSomeObj { + auto *obj = [self getSomeObj]; + [obj doWork]; +} +@end From e250d22feb65d1ab832b046ccfeb852fed7f398e Mon Sep 17 00:00:00 2001 From: Ryosuke Niwa Date: Fri, 4 Apr 2025 12:04:20 -0700 Subject: [PATCH 20/31] [alpha.webkit.ForwardDeclChecker] Ignore forward declared struct. (#133804) There are some system libraries such as sqlite3 which forward declare a struct then use a pointer to that forward declared type in various APIs. Ignore these types ForwardDeclChecker like other pointer types. --- .../Checkers/WebKit/ForwardDeclChecker.cpp | 12 ++++++------ .../Analysis/Checkers/WebKit/forward-decl-checker.mm | 4 ++++ .../Analysis/Checkers/WebKit/mock-system-header.h | 2 ++ 3 files changed, 12 insertions(+), 6 deletions(-) diff --git a/clang/lib/StaticAnalyzer/Checkers/WebKit/ForwardDeclChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/WebKit/ForwardDeclChecker.cpp index a524593b0119b..2c63224df129a 100644 --- a/clang/lib/StaticAnalyzer/Checkers/WebKit/ForwardDeclChecker.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/WebKit/ForwardDeclChecker.cpp @@ -108,17 +108,16 @@ class ForwardDeclChecker : public Checker> { RTC.visitTypedef(TD); auto QT = TD->getUnderlyingType().getCanonicalType(); if (BR->getSourceManager().isInSystemHeader(TD->getBeginLoc())) { - if (auto *Type = QT.getTypePtrOrNull(); Type && QT->isPointerType()) + if (auto *Type = QT.getTypePtrOrNull()) SystemTypes.insert(Type); } } bool isUnknownType(QualType QT) const { - auto *Type = QT.getTypePtrOrNull(); - if (!Type) - return false; auto *CanonicalType = QT.getCanonicalType().getTypePtrOrNull(); - auto PointeeQT = Type->getPointeeType(); + if (!CanonicalType) + return false; + auto PointeeQT = CanonicalType->getPointeeType(); auto *PointeeType = PointeeQT.getTypePtrOrNull(); if (!PointeeType) return false; @@ -128,7 +127,8 @@ class ForwardDeclChecker : public Checker> { auto Name = R->getName(); return !R->hasDefinition() && !RTC.isUnretained(QT) && !SystemTypes.contains(CanonicalType) && - !Name.starts_with("Opaque") && Name != "_NSZone"; + !SystemTypes.contains(PointeeType) && !Name.starts_with("Opaque") && + Name != "_NSZone"; } void visitRecordDecl(const RecordDecl *RD, const Decl *DeclWithIssue) const { diff --git a/clang/test/Analysis/Checkers/WebKit/forward-decl-checker.mm b/clang/test/Analysis/Checkers/WebKit/forward-decl-checker.mm index 151cbe2affa92..64100d60c4867 100644 --- a/clang/test/Analysis/Checkers/WebKit/forward-decl-checker.mm +++ b/clang/test/Analysis/Checkers/WebKit/forward-decl-checker.mm @@ -25,6 +25,8 @@ Obj* provide_obj_ptr(); void receive_obj_ptr(Obj* p = nullptr); +sqlite3* open_db(); +void close_db(sqlite3*); Obj* ptr(Obj* arg) { receive_obj_ptr(provide_obj_ptr()); @@ -34,6 +36,8 @@ receive_obj_ptr(arg); receive_obj_ptr(nullptr); receive_obj_ptr(); + auto* db = open_db(); + close_db(db); return obj; } diff --git a/clang/test/Analysis/Checkers/WebKit/mock-system-header.h b/clang/test/Analysis/Checkers/WebKit/mock-system-header.h index 450fb24687343..1e44de8eb62ad 100644 --- a/clang/test/Analysis/Checkers/WebKit/mock-system-header.h +++ b/clang/test/Analysis/Checkers/WebKit/mock-system-header.h @@ -16,6 +16,8 @@ struct MemberVariable { T* obj { nullptr }; }; +typedef struct sqlite3 sqlite3; + typedef unsigned char uint8_t; enum os_log_type_t : uint8_t { From fcfed418c634e9b785dc2469aff79a5e9ff79423 Mon Sep 17 00:00:00 2001 From: Ryosuke Niwa Date: Wed, 9 Apr 2025 10:33:08 -0700 Subject: [PATCH 21/31] [alpha.webkit.RetainPtrCtorAdoptChecker] Recognize mutableCopy from literal as +1 (#132350) This PR adds the support for recognizing the return value of copy / mutableCopy as +1. isAllocInit and isOwned now traverses PseudoObjectExpr to its last semantic expression. --- .../WebKit/RetainPtrCtorAdoptChecker.cpp | 12 ++++++++++++ .../Analysis/Checkers/WebKit/objc-mock-types.h | 5 +++++ .../WebKit/retain-ptr-ctor-adopt-use-arc.mm | 16 ++++++++++++++++ .../Checkers/WebKit/retain-ptr-ctor-adopt-use.mm | 16 ++++++++++++++++ 4 files changed, 49 insertions(+) diff --git a/clang/lib/StaticAnalyzer/Checkers/WebKit/RetainPtrCtorAdoptChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/WebKit/RetainPtrCtorAdoptChecker.cpp index 4ce3262e90e13..1f9a4712cedf6 100644 --- a/clang/lib/StaticAnalyzer/Checkers/WebKit/RetainPtrCtorAdoptChecker.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/WebKit/RetainPtrCtorAdoptChecker.cpp @@ -202,6 +202,12 @@ class RetainPtrCtorAdoptChecker bool isAllocInit(const Expr *E) const { auto *ObjCMsgExpr = dyn_cast(E); + if (auto *POE = dyn_cast(E)) { + if (unsigned ExprCount = POE->getNumSemanticExprs()) { + auto *Expr = POE->getSemanticExpr(ExprCount - 1)->IgnoreParenCasts(); + ObjCMsgExpr = dyn_cast(Expr); + } + } if (!ObjCMsgExpr) return false; auto Selector = ObjCMsgExpr->getSelector(); @@ -247,6 +253,12 @@ class RetainPtrCtorAdoptChecker enum class IsOwnedResult { Unknown, Skip, Owned, NotOwned }; IsOwnedResult isOwned(const Expr *E) const { while (1) { + if (auto *POE = dyn_cast(E)) { + if (unsigned SemanticExprCount = POE->getNumSemanticExprs()) { + E = POE->getSemanticExpr(SemanticExprCount - 1); + continue; + } + } if (isa(E)) return IsOwnedResult::NotOwned; if (auto *DRE = dyn_cast(E)) { diff --git a/clang/test/Analysis/Checkers/WebKit/objc-mock-types.h b/clang/test/Analysis/Checkers/WebKit/objc-mock-types.h index 059a203e3b0d1..9eb63b4745b21 100644 --- a/clang/test/Analysis/Checkers/WebKit/objc-mock-types.h +++ b/clang/test/Analysis/Checkers/WebKit/objc-mock-types.h @@ -1,6 +1,8 @@ @class NSString; @class NSArray; @class NSMutableArray; +@class NSDictionary; +@class NSMutableDictionary; #define nil ((id)0) #define CF_BRIDGED_TYPE(T) __attribute__((objc_bridge(T))) #define CF_BRIDGED_MUTABLE_TYPE(T) __attribute__((objc_bridge_mutable(T))) @@ -95,12 +97,14 @@ __attribute__((objc_root_class)) - (NSEnumerator *)keyEnumerator; + (id)dictionary; + (id)dictionaryWithObject:(id)object forKey:(id )key; +- (NSMutableDictionary *)mutableCopy; + (instancetype)dictionaryWithObjects:(const id [])objects forKeys:(const id [])keys count:(NSUInteger)cnt; @end @interface NSArray : NSObject - (NSUInteger)count; - (NSEnumerator *)objectEnumerator; +- (NSMutableArray *)mutableCopy; + (NSArray *)arrayWithObjects:(const id [])objects count:(NSUInteger)count; @end @@ -109,6 +113,7 @@ __attribute__((objc_root_class)) - (NSString *)stringByAppendingString:(NSString *)aString; - ( const char *)UTF8String; - (id)initWithUTF8String:(const char *)nullTerminatedCString; +- (NSString *)copy; + (id)stringWithUTF8String:(const char *)nullTerminatedCString; @end diff --git a/clang/test/Analysis/Checkers/WebKit/retain-ptr-ctor-adopt-use-arc.mm b/clang/test/Analysis/Checkers/WebKit/retain-ptr-ctor-adopt-use-arc.mm index 99f488b578910..c06d22210c3ee 100644 --- a/clang/test/Analysis/Checkers/WebKit/retain-ptr-ctor-adopt-use-arc.mm +++ b/clang/test/Analysis/Checkers/WebKit/retain-ptr-ctor-adopt-use-arc.mm @@ -96,3 +96,19 @@ void create_member_init() { RetainPtr return_bridge_cast() { return bridge_cast(create_cf_array()); } + +void mutable_copy_dictionary() { + RetainPtr mutableDictionary = adoptNS(@{ + @"Content-Type": @"text/html", + }.mutableCopy); +} + +void mutable_copy_array() { + RetainPtr mutableArray = adoptNS(@[ + @"foo", + ].mutableCopy); +} + +void string_copy(NSString *str) { + RetainPtr copy = adoptNS(str.copy); +} diff --git a/clang/test/Analysis/Checkers/WebKit/retain-ptr-ctor-adopt-use.mm b/clang/test/Analysis/Checkers/WebKit/retain-ptr-ctor-adopt-use.mm index 111342bc0e388..f6603be00b3ec 100644 --- a/clang/test/Analysis/Checkers/WebKit/retain-ptr-ctor-adopt-use.mm +++ b/clang/test/Analysis/Checkers/WebKit/retain-ptr-ctor-adopt-use.mm @@ -96,3 +96,19 @@ void create_member_init() { RetainPtr return_bridge_cast() { return bridge_cast(create_cf_array()); } + +void mutable_copy_dictionary() { + RetainPtr mutableDictionary = adoptNS(@{ + @"Content-Type": @"text/html", + }.mutableCopy); +} + +void mutable_copy_array() { + RetainPtr mutableArray = adoptNS(@[ + @"foo", + ].mutableCopy); +} + +void string_copy(NSString *str) { + RetainPtr copy = adoptNS(str.copy); +} From 81d9f2f6b487ff9dbcf35e7c35da6a6b9ffaa46f Mon Sep 17 00:00:00 2001 From: Ryosuke Niwa Date: Wed, 9 Apr 2025 11:52:36 -0700 Subject: [PATCH 22/31] [alpha.webkit.UnretainedLambdaCapturesChecker] Add the support for protectedSelf (#132518) This PR adds the support for treating capturing of "self" as safe if the lambda simultaneously captures "protectedSelf", which is a RetainPtr of "self". This PR also fixes a bug that the checker wasn't generating a warning when "self" is implicitly captured. Note when "self" is implicitly captured, we use the lambda's getBeginLoc as a fallback source location. --- .../Checkers/WebKit/PtrTypesSemantics.cpp | 2 +- .../WebKit/RawPtrRefLambdaCapturesChecker.cpp | 61 +++++++++++++++---- .../WebKit/unretained-lambda-captures-arc.mm | 22 ++++++- .../WebKit/unretained-lambda-captures.mm | 24 +++++++- 4 files changed, 95 insertions(+), 14 deletions(-) diff --git a/clang/lib/StaticAnalyzer/Checkers/WebKit/PtrTypesSemantics.cpp b/clang/lib/StaticAnalyzer/Checkers/WebKit/PtrTypesSemantics.cpp index 7bc04ee565d03..430f1c6db50b4 100644 --- a/clang/lib/StaticAnalyzer/Checkers/WebKit/PtrTypesSemantics.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/WebKit/PtrTypesSemantics.cpp @@ -147,7 +147,7 @@ bool isCtorOfCheckedPtr(const clang::FunctionDecl *F) { bool isCtorOfRetainPtr(const clang::FunctionDecl *F) { const std::string &FunctionName = safeGetName(F); return FunctionName == "RetainPtr" || FunctionName == "adoptNS" || - FunctionName == "adoptCF"; + FunctionName == "adoptCF" || FunctionName == "retainPtr"; } bool isCtorOfSafePtr(const clang::FunctionDecl *F) { diff --git a/clang/lib/StaticAnalyzer/Checkers/WebKit/RawPtrRefLambdaCapturesChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/WebKit/RawPtrRefLambdaCapturesChecker.cpp index fdbd8e2922438..a7492170d0ec2 100644 --- a/clang/lib/StaticAnalyzer/Checkers/WebKit/RawPtrRefLambdaCapturesChecker.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/WebKit/RawPtrRefLambdaCapturesChecker.cpp @@ -36,6 +36,7 @@ class RawPtrRefLambdaCapturesChecker : Bug(this, description, "WebKit coding guidelines") {} virtual std::optional isUnsafePtr(QualType) const = 0; + virtual bool isPtrType(const std::string &) const = 0; virtual const char *ptrKind(QualType QT) const = 0; void checkASTDecl(const TranslationUnitDecl *TUD, AnalysisManager &MGR, @@ -71,6 +72,15 @@ class RawPtrRefLambdaCapturesChecker return Base::TraverseCXXMethodDecl(CXXMD); } + bool TraverseObjCMethodDecl(ObjCMethodDecl *OCMD) { + llvm::SaveAndRestore SavedDecl(ClsType); + if (OCMD && OCMD->isInstanceMethod()) { + if (auto *ImplParamDecl = OCMD->getSelfDecl()) + ClsType = ImplParamDecl->getType(); + } + return Base::TraverseObjCMethodDecl(OCMD); + } + bool VisitTypedefDecl(TypedefDecl *TD) { if (Checker->RTC) Checker->RTC->visitTypedef(TD); @@ -278,10 +288,10 @@ class RawPtrRefLambdaCapturesChecker auto *VD = dyn_cast(ValueDecl); if (!VD) return false; - auto *Init = VD->getInit()->IgnoreParenCasts(); + auto *Init = VD->getInit(); if (!Init) return false; - const Expr *Arg = Init; + const Expr *Arg = Init->IgnoreParenCasts(); do { if (auto *BTE = dyn_cast(Arg)) Arg = BTE->getSubExpr()->IgnoreParenCasts(); @@ -290,7 +300,7 @@ class RawPtrRefLambdaCapturesChecker if (!Ctor) return false; auto clsName = safeGetName(Ctor->getParent()); - if (isRefType(clsName) && CE->getNumArgs()) { + if (Checker->isPtrType(clsName) && CE->getNumArgs()) { Arg = CE->getArg(0)->IgnoreParenCasts(); continue; } @@ -310,6 +320,12 @@ class RawPtrRefLambdaCapturesChecker Arg = CE->getArg(0)->IgnoreParenCasts(); continue; } + if (auto *Callee = CE->getDirectCallee()) { + if (isCtorOfSafePtr(Callee) && CE->getNumArgs() == 1) { + Arg = CE->getArg(0)->IgnoreParenCasts(); + continue; + } + } } if (auto *OpCE = dyn_cast(Arg)) { auto OpCode = OpCE->getOperator(); @@ -318,7 +334,7 @@ class RawPtrRefLambdaCapturesChecker if (!Callee) return false; auto clsName = safeGetName(Callee->getParent()); - if (!isRefType(clsName) || !OpCE->getNumArgs()) + if (!Checker->isPtrType(clsName) || !OpCE->getNumArgs()) return false; Arg = OpCE->getArg(0)->IgnoreParenCasts(); continue; @@ -333,8 +349,15 @@ class RawPtrRefLambdaCapturesChecker } break; } while (Arg); - if (auto *DRE = dyn_cast(Arg)) - return ProtectedThisDecls.contains(DRE->getDecl()); + if (auto *DRE = dyn_cast(Arg)) { + auto *Decl = DRE->getDecl(); + if (auto *ImplicitParam = dyn_cast(Decl)) { + auto kind = ImplicitParam->getParameterKind(); + return kind == ImplicitParamKind::ObjCSelf || + kind == ImplicitParamKind::CXXThis; + } + return ProtectedThisDecls.contains(Decl); + } return isa(Arg); } }; @@ -354,10 +377,17 @@ class RawPtrRefLambdaCapturesChecker ValueDecl *CapturedVar = C.getCapturedVar(); if (ignoreParamVarDecl && isa(CapturedVar)) continue; + if (auto *ImplicitParam = dyn_cast(CapturedVar)) { + auto kind = ImplicitParam->getParameterKind(); + if ((kind == ImplicitParamKind::ObjCSelf || + kind == ImplicitParamKind::CXXThis) && + !shouldCheckThis) + continue; + } QualType CapturedVarQualType = CapturedVar->getType(); auto IsUncountedPtr = isUnsafePtr(CapturedVar->getType()); if (IsUncountedPtr && *IsUncountedPtr) - reportBug(C, CapturedVar, CapturedVarQualType); + reportBug(C, CapturedVar, CapturedVarQualType, L); } else if (C.capturesThis() && shouldCheckThis) { if (ignoreParamVarDecl) // this is always a parameter to this function. continue; @@ -367,11 +397,12 @@ class RawPtrRefLambdaCapturesChecker } void reportBug(const LambdaCapture &Capture, ValueDecl *CapturedVar, - const QualType T) const { + const QualType T, LambdaExpr *L) const { assert(CapturedVar); - if (isa(CapturedVar) && !Capture.getLocation().isValid()) - return; // Ignore implicit captruing of self. + auto Location = Capture.getLocation(); + if (isa(CapturedVar) && !Location.isValid()) + Location = L->getBeginLoc(); SmallString<100> Buf; llvm::raw_svector_ostream Os(Buf); @@ -390,7 +421,7 @@ class RawPtrRefLambdaCapturesChecker printQuotedQualifiedName(Os, CapturedVar); Os << " to " << ptrKind(T) << " type is unsafe."; - PathDiagnosticLocation BSLoc(Capture.getLocation(), BR->getSourceManager()); + PathDiagnosticLocation BSLoc(Location, BR->getSourceManager()); auto Report = std::make_unique(Bug, Os.str(), BSLoc); BR->emitReport(std::move(Report)); } @@ -432,6 +463,10 @@ class UncountedLambdaCapturesChecker : public RawPtrRefLambdaCapturesChecker { return result2; } + virtual bool isPtrType(const std::string &Name) const final { + return isRefType(Name) || isCheckedPtr(Name); + } + const char *ptrKind(QualType QT) const final { if (isUncounted(QT)) return "uncounted"; @@ -451,6 +486,10 @@ class UnretainedLambdaCapturesChecker : public RawPtrRefLambdaCapturesChecker { return RTC->isUnretained(QT); } + virtual bool isPtrType(const std::string &Name) const final { + return isRetainPtr(Name); + } + const char *ptrKind(QualType QT) const final { return "unretained"; } }; diff --git a/clang/test/Analysis/Checkers/WebKit/unretained-lambda-captures-arc.mm b/clang/test/Analysis/Checkers/WebKit/unretained-lambda-captures-arc.mm index 4e3d9c2708d96..63526a5047157 100644 --- a/clang/test/Analysis/Checkers/WebKit/unretained-lambda-captures-arc.mm +++ b/clang/test/Analysis/Checkers/WebKit/unretained-lambda-captures-arc.mm @@ -256,6 +256,7 @@ @interface ObjWithSelf : NSObject { RetainPtr delegate; } -(void)doWork; +-(void)doMoreWork; -(void)run; @end @@ -265,9 +266,28 @@ -(void)doWork { someFunction(); [delegate doWork]; }; + auto doMoreWork = [=] { + someFunction(); + [delegate doWork]; + }; + auto doExtraWork = [&, protectedSelf = retainPtr(self)] { + someFunction(); + [delegate doWork]; + }; callFunctionOpaque(doWork); + callFunctionOpaque(doMoreWork); + callFunctionOpaque(doExtraWork); } + +-(void)doMoreWork { + auto doWork = [self, protectedSelf = retainPtr(self)] { + someFunction(); + [self doWork]; + }; + callFunctionOpaque(doWork); +} + -(void)run { someFunction(); } -@end \ No newline at end of file +@end diff --git a/clang/test/Analysis/Checkers/WebKit/unretained-lambda-captures.mm b/clang/test/Analysis/Checkers/WebKit/unretained-lambda-captures.mm index 073eff9386baa..dcc6672261d8c 100644 --- a/clang/test/Analysis/Checkers/WebKit/unretained-lambda-captures.mm +++ b/clang/test/Analysis/Checkers/WebKit/unretained-lambda-captures.mm @@ -279,18 +279,40 @@ @interface ObjWithSelf : NSObject { RetainPtr delegate; } -(void)doWork; +-(void)doMoreWork; -(void)run; @end @implementation ObjWithSelf -(void)doWork { auto doWork = [&] { + // expected-warning@-1{{Implicitly captured raw-pointer 'self' to unretained type is unsafe [alpha.webkit.UnretainedLambdaCapturesChecker]}} + someFunction(); + [delegate doWork]; + }; + auto doMoreWork = [=] { + // expected-warning@-1{{Implicitly captured raw-pointer 'self' to unretained type is unsafe [alpha.webkit.UnretainedLambdaCapturesChecker]}} + someFunction(); + [delegate doWork]; + }; + auto doExtraWork = [&, protectedSelf = retainPtr(self)] { someFunction(); [delegate doWork]; }; callFunctionOpaque(doWork); + callFunctionOpaque(doMoreWork); + callFunctionOpaque(doExtraWork); } + +-(void)doMoreWork { + auto doWork = [self, protectedSelf = retainPtr(self)] { + someFunction(); + [self doWork]; + }; + callFunctionOpaque(doWork); +} + -(void)run { someFunction(); } -@end \ No newline at end of file +@end From 9ecfa43e30ad36186e8f1dce2ef208a994949f3e Mon Sep 17 00:00:00 2001 From: Ryosuke Niwa Date: Thu, 10 Apr 2025 15:26:10 -0700 Subject: [PATCH 23/31] [alpha.webkit.RetainPtrCtorAdoptChecker] Support adopt(cast(copy(~)) (#132316) This PR adds the support for recognizing calling adoptCF/adoptNS on the result of a cast operation on the return value of a function which creates NS or CF types. It also fixes a bug that we weren't reporting memory leaks when CF types are created without ever calling RetainPtr's constructor, adoptCF, or adoptNS. To do this, this PR adds a new mechanism to report a memory leak whenever create or copy CF functions are invoked unless this CallExpr has already been visited while validating a call to adoptCF. Also added an early exit when isOwned returns IsOwnedResult::Skip due to an unresolved template argument. --- .../Checkers/WebKit/PtrTypesSemantics.cpp | 28 +++++++- .../Checkers/WebKit/PtrTypesSemantics.h | 4 ++ .../WebKit/RawPtrRefCallArgsChecker.cpp | 2 +- .../WebKit/RawPtrRefLocalVarsChecker.cpp | 6 ++ .../WebKit/RetainPtrCtorAdoptChecker.cpp | 71 +++++++++++++++---- .../Checkers/WebKit/objc-mock-types.h | 39 +++++----- .../WebKit/retain-ptr-ctor-adopt-use-arc.mm | 9 ++- .../WebKit/retain-ptr-ctor-adopt-use.mm | 11 ++- 8 files changed, 132 insertions(+), 38 deletions(-) diff --git a/clang/lib/StaticAnalyzer/Checkers/WebKit/PtrTypesSemantics.cpp b/clang/lib/StaticAnalyzer/Checkers/WebKit/PtrTypesSemantics.cpp index 430f1c6db50b4..781b0de5abd2f 100644 --- a/clang/lib/StaticAnalyzer/Checkers/WebKit/PtrTypesSemantics.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/WebKit/PtrTypesSemantics.cpp @@ -125,6 +125,16 @@ bool isCheckedPtr(const std::string &Name) { return Name == "CheckedPtr" || Name == "CheckedRef"; } +bool isSmartPtrClass(const std::string &Name) { + return isRefType(Name) || isCheckedPtr(Name) || isRetainPtr(Name) || + Name == "WeakPtr" || Name == "WeakPtr" || Name == "WeakPtrFactory" || + Name == "WeakPtrFactoryWithBitField" || Name == "WeakPtrImplBase" || + Name == "WeakPtrImplBaseSingleThread" || Name == "ThreadSafeWeakPtr" || + Name == "ThreadSafeWeakOrStrongPtr" || + Name == "ThreadSafeWeakPtrControlBlock" || + Name == "ThreadSafeRefCountedAndCanMakeThreadSafeWeakPtr"; +} + bool isCtorOfRefCounted(const clang::FunctionDecl *F) { assert(F); const std::string &FunctionName = safeGetName(F); @@ -222,8 +232,13 @@ void RetainTypeChecker::visitTypedef(const TypedefDecl *TD) { auto PointeeQT = QT->getPointeeType(); const RecordType *RT = PointeeQT->getAs(); - if (!RT) + if (!RT) { + if (TD->hasAttr() || TD->hasAttr()) { + if (auto *Type = TD->getTypeForDecl()) + RecordlessTypes.insert(Type); + } return; + } for (auto *Redecl : RT->getDecl()->getMostRecentDecl()->redecls()) { if (Redecl->getAttr() || @@ -240,6 +255,17 @@ bool RetainTypeChecker::isUnretained(const QualType QT, bool ignoreARC) { auto CanonicalType = QT.getCanonicalType(); auto PointeeType = CanonicalType->getPointeeType(); auto *RT = dyn_cast_or_null(PointeeType.getTypePtrOrNull()); + if (!RT) { + auto *Type = QT.getTypePtrOrNull(); + while (Type) { + if (RecordlessTypes.contains(Type)) + return true; + auto *ET = dyn_cast_or_null(Type); + if (!ET) + break; + Type = ET->desugar().getTypePtrOrNull(); + } + } return RT && CFPointees.contains(RT); } diff --git a/clang/lib/StaticAnalyzer/Checkers/WebKit/PtrTypesSemantics.h b/clang/lib/StaticAnalyzer/Checkers/WebKit/PtrTypesSemantics.h index 096675fb912f2..97c9d0510e67d 100644 --- a/clang/lib/StaticAnalyzer/Checkers/WebKit/PtrTypesSemantics.h +++ b/clang/lib/StaticAnalyzer/Checkers/WebKit/PtrTypesSemantics.h @@ -70,6 +70,7 @@ std::optional isUnchecked(const clang::QualType T); /// underlying pointer type. class RetainTypeChecker { llvm::DenseSet CFPointees; + llvm::DenseSet RecordlessTypes; bool IsARCEnabled{false}; public: @@ -135,6 +136,9 @@ bool isCheckedPtr(const std::string &Name); /// \returns true if \p Name is RetainPtr or its variant, false if not. bool isRetainPtr(const std::string &Name); +/// \returns true if \p Name is a smart pointer type name, false if not. +bool isSmartPtrClass(const std::string &Name); + /// \returns true if \p M is getter of a ref-counted class, false if not. std::optional isGetterOfSafePtr(const clang::CXXMethodDecl *Method); diff --git a/clang/lib/StaticAnalyzer/Checkers/WebKit/RawPtrRefCallArgsChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/WebKit/RawPtrRefCallArgsChecker.cpp index b1cfa2afe1227..e4ed0ab3f00c1 100644 --- a/clang/lib/StaticAnalyzer/Checkers/WebKit/RawPtrRefCallArgsChecker.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/WebKit/RawPtrRefCallArgsChecker.cpp @@ -72,7 +72,7 @@ class RawPtrRefCallArgsChecker bool shouldVisitImplicitCode() const { return false; } bool TraverseClassTemplateDecl(ClassTemplateDecl *Decl) { - if (isRefType(safeGetName(Decl))) + if (isSmartPtrClass(safeGetName(Decl))) return true; return Base::TraverseClassTemplateDecl(Decl); } diff --git a/clang/lib/StaticAnalyzer/Checkers/WebKit/RawPtrRefLocalVarsChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/WebKit/RawPtrRefLocalVarsChecker.cpp index d0a5c53a13e82..bdb2d102f96a1 100644 --- a/clang/lib/StaticAnalyzer/Checkers/WebKit/RawPtrRefLocalVarsChecker.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/WebKit/RawPtrRefLocalVarsChecker.cpp @@ -267,6 +267,12 @@ class RawPtrRefLocalVarsChecker return Base::TraverseCompoundStmt(CS); return true; } + + bool TraverseClassTemplateDecl(ClassTemplateDecl *Decl) { + if (isSmartPtrClass(safeGetName(Decl))) + return true; + return Base::TraverseClassTemplateDecl(Decl); + } }; LocalVisitor visitor(this); diff --git a/clang/lib/StaticAnalyzer/Checkers/WebKit/RetainPtrCtorAdoptChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/WebKit/RetainPtrCtorAdoptChecker.cpp index 1f9a4712cedf6..df699d2c7f2fd 100644 --- a/clang/lib/StaticAnalyzer/Checkers/WebKit/RetainPtrCtorAdoptChecker.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/WebKit/RetainPtrCtorAdoptChecker.cpp @@ -34,6 +34,7 @@ class RetainPtrCtorAdoptChecker mutable BugReporter *BR; mutable std::unique_ptr Summaries; mutable llvm::DenseSet CreateOrCopyOutArguments; + mutable llvm::DenseSet CreateOrCopyFnCall; mutable RetainTypeChecker RTC; public: @@ -119,7 +120,7 @@ class RetainPtrCtorAdoptChecker return; if (!isAdoptFn(F) || !CE->getNumArgs()) { - rememberOutArguments(CE, F); + checkCreateOrCopyFunction(CE, F, DeclWithIssue); return; } @@ -128,24 +129,29 @@ class RetainPtrCtorAdoptChecker auto Name = safeGetName(F); if (Result == IsOwnedResult::Unknown) Result = IsOwnedResult::NotOwned; - if (Result == IsOwnedResult::NotOwned && !isAllocInit(Arg) && - !isCreateOrCopy(Arg)) { - if (auto *DRE = dyn_cast(Arg)) { - if (CreateOrCopyOutArguments.contains(DRE->getDecl())) - return; - } - if (RTC.isARCEnabled() && isAdoptNS(F)) - reportUseAfterFree(Name, CE, DeclWithIssue, "when ARC is disabled"); - else - reportUseAfterFree(Name, CE, DeclWithIssue); + if (isAllocInit(Arg) || isCreateOrCopy(Arg)) { + CreateOrCopyFnCall.insert(Arg); // Avoid double reporting. + return; } + if (Result == IsOwnedResult::Owned || Result == IsOwnedResult::Skip) + return; + + if (auto *DRE = dyn_cast(Arg)) { + if (CreateOrCopyOutArguments.contains(DRE->getDecl())) + return; + } + if (RTC.isARCEnabled() && isAdoptNS(F)) + reportUseAfterFree(Name, CE, DeclWithIssue, "when ARC is disabled"); + else + reportUseAfterFree(Name, CE, DeclWithIssue); } - void rememberOutArguments(const CallExpr *CE, - const FunctionDecl *Callee) const { + void checkCreateOrCopyFunction(const CallExpr *CE, const FunctionDecl *Callee, + const Decl *DeclWithIssue) const { if (!isCreateOrCopyFunction(Callee)) return; + bool hasOutArgument = false; unsigned ArgCount = CE->getNumArgs(); for (unsigned ArgIndex = 0; ArgIndex < ArgCount; ++ArgIndex) { auto *Arg = CE->getArg(ArgIndex)->IgnoreParenCasts(); @@ -164,7 +170,12 @@ class RetainPtrCtorAdoptChecker if (!Decl) continue; CreateOrCopyOutArguments.insert(Decl); + hasOutArgument = true; } + if (!RTC.isUnretained(Callee->getReturnType())) + return; + if (!hasOutArgument && !CreateOrCopyFnCall.contains(CE)) + reportLeak(CE, DeclWithIssue); } void visitConstructExpr(const CXXConstructExpr *CE, @@ -190,6 +201,13 @@ class RetainPtrCtorAdoptChecker std::string Name = "RetainPtr constructor"; auto *Arg = CE->getArg(0)->IgnoreParenCasts(); auto Result = isOwned(Arg); + + if (isCreateOrCopy(Arg)) + CreateOrCopyFnCall.insert(Arg); // Avoid double reporting. + + if (Result == IsOwnedResult::Skip) + return; + if (Result == IsOwnedResult::Unknown) Result = IsOwnedResult::NotOwned; if (Result == IsOwnedResult::Owned) @@ -315,11 +333,22 @@ class RetainPtrCtorAdoptChecker if (auto *Callee = CE->getDirectCallee()) { if (isAdoptFn(Callee)) return IsOwnedResult::NotOwned; - if (safeGetName(Callee) == "__builtin___CFStringMakeConstantString") + auto Name = safeGetName(Callee); + if (Name == "__builtin___CFStringMakeConstantString") return IsOwnedResult::NotOwned; + if ((Name == "checked_cf_cast" || Name == "dynamic_cf_cast" || + Name == "checked_objc_cast" || Name == "dynamic_objc_cast") && + CE->getNumArgs() == 1) { + E = CE->getArg(0)->IgnoreParenCasts(); + continue; + } auto RetType = Callee->getReturnType(); if (isRetainPtrType(RetType)) return IsOwnedResult::NotOwned; + if (isCreateOrCopyFunction(Callee)) { + CreateOrCopyFnCall.insert(CE); + return IsOwnedResult::Owned; + } } else if (auto *CalleeExpr = CE->getCallee()) { if (isa(CalleeExpr)) return IsOwnedResult::Skip; // Wait for instantiation. @@ -383,6 +412,20 @@ class RetainPtrCtorAdoptChecker Report->setDeclWithIssue(DeclWithIssue); BR->emitReport(std::move(Report)); } + + void reportLeak(const CallExpr *CE, const Decl *DeclWithIssue) const { + SmallString<100> Buf; + llvm::raw_svector_ostream Os(Buf); + + Os << "The return value is +1 and results in a memory leak."; + + PathDiagnosticLocation BSLoc(CE->getSourceRange().getBegin(), + BR->getSourceManager()); + auto Report = std::make_unique(Bug, Os.str(), BSLoc); + Report->addRange(CE->getSourceRange()); + Report->setDeclWithIssue(DeclWithIssue); + BR->emitReport(std::move(Report)); + } }; } // namespace diff --git a/clang/test/Analysis/Checkers/WebKit/objc-mock-types.h b/clang/test/Analysis/Checkers/WebKit/objc-mock-types.h index 9eb63b4745b21..3f075ca0a6e5b 100644 --- a/clang/test/Analysis/Checkers/WebKit/objc-mock-types.h +++ b/clang/test/Analysis/Checkers/WebKit/objc-mock-types.h @@ -24,6 +24,9 @@ typedef struct CF_BRIDGED_MUTABLE_TYPE(CFRunLoopRef) __CFRunLoop * CFRunLoopRef; extern const CFAllocatorRef kCFAllocatorDefault; typedef struct _NSZone NSZone; + +CFTypeID CFGetTypeID(CFTypeRef cf); + CFTypeID CFGetTypeID(CFTypeRef cf); CFTypeID CFArrayGetTypeID(); CFMutableArrayRef CFArrayCreateMutable(CFAllocatorRef allocator, CFIndex capacity); @@ -281,12 +284,12 @@ template inline RetainPtr retainPtr(T* ptr) inline NSObject *bridge_cast(CFTypeRef object) { - return (__bridge NSObject *)object; + return (__bridge NSObject *)object; } inline CFTypeRef bridge_cast(NSObject *object) { - return (__bridge CFTypeRef)object; + return (__bridge CFTypeRef)object; } inline id bridge_id_cast(CFTypeRef object) @@ -386,35 +389,35 @@ template struct CFTypeTrait; template T dynamic_cf_cast(CFTypeRef object) { - if (!object) - return nullptr; + if (!object) + return nullptr; - if (CFGetTypeID(object) != CFTypeTrait::typeID()) - return nullptr; + if (CFGetTypeID(object) != CFTypeTrait::typeID()) + return nullptr; - return static_cast(const_cast(object)); + return static_cast(const_cast(object)); } template T checked_cf_cast(CFTypeRef object) { - if (!object) - return nullptr; + if (!object) + return nullptr; - if (CFGetTypeID(object) != CFTypeTrait::typeID()) - WTFCrash(); + if (CFGetTypeID(object) != CFTypeTrait::typeID()) + WTFCrash(); - return static_cast(const_cast(object)); + return static_cast(const_cast(object)); } template RetainPtr dynamic_cf_cast(RetainPtr&& object) { - if (!object) - return nullptr; + if (!object) + return nullptr; - if (CFGetTypeID(object.get()) != CFTypeTrait::typeID()) - return nullptr; + if (CFGetTypeID(object.get()) != CFTypeTrait::typeID()) + return nullptr; - return adoptCF(static_cast(const_cast(object.leakRef()))); + return adoptCF(static_cast(const_cast(object.leakRef()))); } } // namespace WTF @@ -448,4 +451,4 @@ using WTF::is_objc; using WTF::checked_objc_cast; using WTF::dynamic_objc_cast; using WTF::checked_cf_cast; -using WTF::dynamic_cf_cast; \ No newline at end of file +using WTF::dynamic_cf_cast; diff --git a/clang/test/Analysis/Checkers/WebKit/retain-ptr-ctor-adopt-use-arc.mm b/clang/test/Analysis/Checkers/WebKit/retain-ptr-ctor-adopt-use-arc.mm index c06d22210c3ee..c43f7f7087088 100644 --- a/clang/test/Analysis/Checkers/WebKit/retain-ptr-ctor-adopt-use-arc.mm +++ b/clang/test/Analysis/Checkers/WebKit/retain-ptr-ctor-adopt-use-arc.mm @@ -2,6 +2,8 @@ #include "objc-mock-types.h" +CFTypeRef CFCopyArray(CFArrayRef); + void basic_correct() { auto ns1 = adoptNS([SomeObj alloc]); auto ns2 = adoptNS([[SomeObj alloc] init]); @@ -11,6 +13,7 @@ void basic_correct() { auto ns6 = retainPtr([ns3 next]); CFMutableArrayRef cf1 = adoptCF(CFArrayCreateMutable(kCFAllocatorDefault, 10)); auto cf2 = adoptCF(SecTaskCreateFromSelf(kCFAllocatorDefault)); + auto cf3 = adoptCF(checked_cf_cast(CFCopyArray(cf1))); } CFMutableArrayRef provide_cf(); @@ -26,6 +29,8 @@ void basic_wrong() { // expected-warning@-1{{Incorrect use of adoptCF. The argument is +0 and results in an use-after-free [alpha.webkit.RetainPtrCtorAdoptChecker]}} RetainPtr cf3 = SecTaskCreateFromSelf(kCFAllocatorDefault); // expected-warning@-1{{Incorrect use of RetainPtr constructor. The argument is +1 and results in a memory leak [alpha.webkit.RetainPtrCtorAdoptChecker]}} + CFCopyArray(cf1); + // expected-warning@-1{{The return value is +1 and results in a memory leak [alpha.webkit.RetainPtrCtorAdoptChecker]}} } RetainPtr cf_out_argument() { @@ -67,7 +72,7 @@ void adopt_retainptr() { class MemberInit { public: - MemberInit(CFMutableArrayRef array, NSString *str, CFRunLoopRef runLoop) + MemberInit(RetainPtr&& array, NSString *str, CFRunLoopRef runLoop) : m_array(array) , m_str(str) , m_runLoop(runLoop) @@ -79,7 +84,7 @@ void adopt_retainptr() { RetainPtr m_runLoop; }; void create_member_init() { - MemberInit init { CFArrayCreateMutable(kCFAllocatorDefault, 10), @"hello", CFRunLoopGetCurrent() }; + MemberInit init { adoptCF(CFArrayCreateMutable(kCFAllocatorDefault, 10)), @"hello", CFRunLoopGetCurrent() }; } RetainPtr cfstr() { diff --git a/clang/test/Analysis/Checkers/WebKit/retain-ptr-ctor-adopt-use.mm b/clang/test/Analysis/Checkers/WebKit/retain-ptr-ctor-adopt-use.mm index f6603be00b3ec..a664b5bc146d0 100644 --- a/clang/test/Analysis/Checkers/WebKit/retain-ptr-ctor-adopt-use.mm +++ b/clang/test/Analysis/Checkers/WebKit/retain-ptr-ctor-adopt-use.mm @@ -2,6 +2,9 @@ #include "objc-mock-types.h" +CFTypeRef CFCopyArray(CFArrayRef); +void* CreateCopy(); + void basic_correct() { auto ns1 = adoptNS([SomeObj alloc]); auto ns2 = adoptNS([[SomeObj alloc] init]); @@ -11,6 +14,8 @@ void basic_correct() { auto ns6 = retainPtr([ns3 next]); CFMutableArrayRef cf1 = adoptCF(CFArrayCreateMutable(kCFAllocatorDefault, 10)); auto cf2 = adoptCF(SecTaskCreateFromSelf(kCFAllocatorDefault)); + auto cf3 = adoptCF(checked_cf_cast(CFCopyArray(cf1))); + CreateCopy(); } CFMutableArrayRef provide_cf(); @@ -26,6 +31,8 @@ void basic_wrong() { // expected-warning@-1{{Incorrect use of adoptCF. The argument is +0 and results in an use-after-free [alpha.webkit.RetainPtrCtorAdoptChecker]}} RetainPtr cf3 = SecTaskCreateFromSelf(kCFAllocatorDefault); // expected-warning@-1{{Incorrect use of RetainPtr constructor. The argument is +1 and results in a memory leak [alpha.webkit.RetainPtrCtorAdoptChecker]}} + CFCopyArray(cf1); + // expected-warning@-1{{The return value is +1 and results in a memory leak [alpha.webkit.RetainPtrCtorAdoptChecker]}} } RetainPtr cf_out_argument() { @@ -67,7 +74,7 @@ void adopt_retainptr() { class MemberInit { public: - MemberInit(CFMutableArrayRef array, NSString *str, CFRunLoopRef runLoop) + MemberInit(RetainPtr&& array, NSString *str, CFRunLoopRef runLoop) : m_array(array) , m_str(str) , m_runLoop(runLoop) @@ -79,7 +86,7 @@ void adopt_retainptr() { RetainPtr m_runLoop; }; void create_member_init() { - MemberInit init { CFArrayCreateMutable(kCFAllocatorDefault, 10), @"hello", CFRunLoopGetCurrent() }; + MemberInit init { adoptCF(CFArrayCreateMutable(kCFAllocatorDefault, 10)), @"hello", CFRunLoopGetCurrent() }; } RetainPtr cfstr() { From 0e333cb131851347841b068683b39ba8cd3d6e50 Mon Sep 17 00:00:00 2001 From: Ryosuke Niwa Date: Thu, 10 Apr 2025 15:28:36 -0700 Subject: [PATCH 24/31] [alpha.webkit.ForwardDeclChecker] Recognize a forward declared template specialization (#134545) This PR fixes a bug that when a template specialization is declared with a forward declaration of a template, the checker fails to find its definition in the same translation unit and erroneously emit an unsafe forward declaration warning. --- .../Checkers/WebKit/ForwardDeclChecker.cpp | 14 ++++++++++++-- .../Checkers/WebKit/forward-decl-checker.mm | 17 +++++++++++++++++ 2 files changed, 29 insertions(+), 2 deletions(-) diff --git a/clang/lib/StaticAnalyzer/Checkers/WebKit/ForwardDeclChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/WebKit/ForwardDeclChecker.cpp index 2c63224df129a..73a0e9eda5b20 100644 --- a/clang/lib/StaticAnalyzer/Checkers/WebKit/ForwardDeclChecker.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/WebKit/ForwardDeclChecker.cpp @@ -125,8 +125,18 @@ class ForwardDeclChecker : public Checker> { if (!R) // Forward declaration of a Objective-C interface is safe. return false; auto Name = R->getName(); - return !R->hasDefinition() && !RTC.isUnretained(QT) && - !SystemTypes.contains(CanonicalType) && + if (R->hasDefinition()) + return false; + // Find a definition amongst template declarations. + if (auto *Specialization = dyn_cast(R)) { + if (auto *S = Specialization->getSpecializedTemplate()) { + for (S = S->getMostRecentDecl(); S; S = S->getPreviousDecl()) { + if (S->isThisDeclarationADefinition()) + return false; + } + } + } + return !RTC.isUnretained(QT) && !SystemTypes.contains(CanonicalType) && !SystemTypes.contains(PointeeType) && !Name.starts_with("Opaque") && Name != "_NSZone"; } diff --git a/clang/test/Analysis/Checkers/WebKit/forward-decl-checker.mm b/clang/test/Analysis/Checkers/WebKit/forward-decl-checker.mm index 64100d60c4867..084b47322d7f9 100644 --- a/clang/test/Analysis/Checkers/WebKit/forward-decl-checker.mm +++ b/clang/test/Analysis/Checkers/WebKit/forward-decl-checker.mm @@ -138,3 +138,20 @@ - (void)doMoreWork:(ObjCObj *)obj { } @end + +namespace template_forward_declare { + +template class HashSet; + +template +using SingleThreadHashSet = HashSet; + +template class HashSet { }; + +struct Font { }; + +struct ComplexTextController { + SingleThreadHashSet* fallbackFonts { nullptr }; +}; + +} From abc6a1288903c415d4136d010678382deef932b1 Mon Sep 17 00:00:00 2001 From: Ryosuke Niwa Date: Mon, 14 Apr 2025 10:27:20 -0700 Subject: [PATCH 25/31] Remove the redundant check for "WeakPtr" in isSmartPtrClass to fix the issue 135612. (#135629) --- clang/lib/StaticAnalyzer/Checkers/WebKit/PtrTypesSemantics.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/clang/lib/StaticAnalyzer/Checkers/WebKit/PtrTypesSemantics.cpp b/clang/lib/StaticAnalyzer/Checkers/WebKit/PtrTypesSemantics.cpp index 781b0de5abd2f..134afcd124526 100644 --- a/clang/lib/StaticAnalyzer/Checkers/WebKit/PtrTypesSemantics.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/WebKit/PtrTypesSemantics.cpp @@ -127,7 +127,7 @@ bool isCheckedPtr(const std::string &Name) { bool isSmartPtrClass(const std::string &Name) { return isRefType(Name) || isCheckedPtr(Name) || isRetainPtr(Name) || - Name == "WeakPtr" || Name == "WeakPtr" || Name == "WeakPtrFactory" || + Name == "WeakPtr" || Name == "WeakPtrFactory" || Name == "WeakPtrFactoryWithBitField" || Name == "WeakPtrImplBase" || Name == "WeakPtrImplBaseSingleThread" || Name == "ThreadSafeWeakPtr" || Name == "ThreadSafeWeakOrStrongPtr" || From 03c248099246cc159e69cda6a6a0a66437855110 Mon Sep 17 00:00:00 2001 From: Ryosuke Niwa Date: Mon, 14 Apr 2025 15:03:21 -0700 Subject: [PATCH 26/31] [alpha.webkit.UnretainedCallArgsChecker] Don't emit a warning for RetainPtr::operator= (#135526) Generalize the check for operator= so that it works for RetainPtr and CheckedPtr instead of just RefPtr. --- .../WebKit/RawPtrRefCallArgsChecker.cpp | 2 +- .../Checkers/WebKit/call-args-checked.cpp | 18 ++++++++++++++---- .../test/Analysis/Checkers/WebKit/mock-types.h | 2 +- .../Analysis/Checkers/WebKit/objc-mock-types.h | 6 +----- .../Checkers/WebKit/unretained-call-args.mm | 10 ++++++++++ 5 files changed, 27 insertions(+), 11 deletions(-) diff --git a/clang/lib/StaticAnalyzer/Checkers/WebKit/RawPtrRefCallArgsChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/WebKit/RawPtrRefCallArgsChecker.cpp index e4ed0ab3f00c1..e0fbee78a0fbc 100644 --- a/clang/lib/StaticAnalyzer/Checkers/WebKit/RawPtrRefCallArgsChecker.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/WebKit/RawPtrRefCallArgsChecker.cpp @@ -267,7 +267,7 @@ class RawPtrRefCallArgsChecker auto *callee = MemberOp->getDirectCallee(); if (auto *calleeDecl = dyn_cast(callee)) { if (const CXXRecordDecl *classDecl = calleeDecl->getParent()) { - if (isRefCounted(classDecl)) + if (isSafePtr(classDecl)) return true; } } diff --git a/clang/test/Analysis/Checkers/WebKit/call-args-checked.cpp b/clang/test/Analysis/Checkers/WebKit/call-args-checked.cpp index 49b6bfcd7cadf..e24b04dcd3cf9 100644 --- a/clang/test/Analysis/Checkers/WebKit/call-args-checked.cpp +++ b/clang/test/Analysis/Checkers/WebKit/call-args-checked.cpp @@ -1,4 +1,4 @@ -// RUN: %clang_analyze_cc1 -analyzer-checker=alpha.webkit.UncountedCallArgsChecker -verify %s +// RUN: %clang_analyze_cc1 -analyzer-checker=alpha.webkit.UncheckedCallArgsChecker -verify %s #include "mock-types.h" @@ -10,10 +10,10 @@ namespace call_args_unchecked_uncounted { static void foo() { someFunction(makeObj()); - // expected-warning@-1{{Call argument is uncounted and unsafe [alpha.webkit.UncountedCallArgsChecker]}} + // expected-warning@-1{{Call argument is unchecked and unsafe [alpha.webkit.UncheckedCallArgsChecker]}} } -} // namespace call_args_checked +} // namespace call_args_unchecked_uncounted namespace call_args_checked { @@ -35,7 +35,7 @@ static void baz() { namespace call_args_default { void someFunction(RefCountableAndCheckable* = makeObj()); -// expected-warning@-1{{Call argument is uncounted and unsafe [alpha.webkit.UncountedCallArgsChecker]}} +// expected-warning@-1{{Call argument is unchecked and unsafe [alpha.webkit.UncheckedCallArgsChecker]}} void otherFunction(RefCountableAndCheckable* = makeObjChecked().ptr()); void foo() { @@ -44,3 +44,13 @@ void foo() { } } + +namespace call_args_checked_assignment { + +CheckedObj* provide(); +void foo() { + CheckedPtr ptr; + ptr = provide(); +} + +} diff --git a/clang/test/Analysis/Checkers/WebKit/mock-types.h b/clang/test/Analysis/Checkers/WebKit/mock-types.h index a1f0cc8b046b9..a03d31870ee0d 100644 --- a/clang/test/Analysis/Checkers/WebKit/mock-types.h +++ b/clang/test/Analysis/Checkers/WebKit/mock-types.h @@ -249,7 +249,7 @@ template struct CheckedPtr { T *get() const { return t; } T *operator->() const { return t; } T &operator*() const { return *t; } - CheckedPtr &operator=(T *) { return *this; } + CheckedPtr &operator=(T *); operator bool() const { return t; } }; diff --git a/clang/test/Analysis/Checkers/WebKit/objc-mock-types.h b/clang/test/Analysis/Checkers/WebKit/objc-mock-types.h index 3f075ca0a6e5b..51de81ac0f033 100644 --- a/clang/test/Analysis/Checkers/WebKit/objc-mock-types.h +++ b/clang/test/Analysis/Checkers/WebKit/objc-mock-types.h @@ -216,11 +216,7 @@ template struct RetainPtr { PtrType get() const { return t; } PtrType operator->() const { return t; } T &operator*() const { return *t; } - RetainPtr &operator=(PtrType t) { - RetainPtr o(t); - swap(o); - return *this; - } + RetainPtr &operator=(PtrType t); PtrType leakRef() { PtrType s = t; diff --git a/clang/test/Analysis/Checkers/WebKit/unretained-call-args.mm b/clang/test/Analysis/Checkers/WebKit/unretained-call-args.mm index 1a93904079444..263514a87896e 100644 --- a/clang/test/Analysis/Checkers/WebKit/unretained-call-args.mm +++ b/clang/test/Analysis/Checkers/WebKit/unretained-call-args.mm @@ -270,6 +270,16 @@ void foo() { } } +namespace cxx_assignment_op { + + SomeObj* provide(); + void foo() { + RetainPtr ptr; + ptr = provide(); + } + +} + namespace call_with_ptr_on_ref { RetainPtr provideProtected(); RetainPtr provideProtectedCF(); From e0eb5fdcd6e80096d92a2eb1f34760c8f1c7ec00 Mon Sep 17 00:00:00 2001 From: Ryosuke Niwa Date: Tue, 15 Apr 2025 20:00:51 -0700 Subject: [PATCH 27/31] [alpha.webkit.UnretainedCallArgsChecker] Add the support for RetainPtrArc (#135532) WebKit uses #define to rename RetainPtr to RetainPtrArc so add the support for it. --- .../Checkers/WebKit/PtrTypesSemantics.cpp | 15 +++++++++------ .../Checkers/WebKit/RetainPtrCtorAdoptChecker.cpp | 8 ++++---- .../Analysis/Checkers/WebKit/objc-mock-types.h | 5 +++++ .../Checkers/WebKit/unretained-call-args-arc.mm | 11 +++++++++++ .../Checkers/WebKit/unretained-call-args.mm | 11 +++++++++++ 5 files changed, 40 insertions(+), 10 deletions(-) diff --git a/clang/lib/StaticAnalyzer/Checkers/WebKit/PtrTypesSemantics.cpp b/clang/lib/StaticAnalyzer/Checkers/WebKit/PtrTypesSemantics.cpp index 134afcd124526..811888e119449 100644 --- a/clang/lib/StaticAnalyzer/Checkers/WebKit/PtrTypesSemantics.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/WebKit/PtrTypesSemantics.cpp @@ -119,7 +119,9 @@ bool isRefType(const std::string &Name) { Name == "RefPtr" || Name == "RefPtrAllowingPartiallyDestroyed"; } -bool isRetainPtr(const std::string &Name) { return Name == "RetainPtr"; } +bool isRetainPtr(const std::string &Name) { + return Name == "RetainPtr" || Name == "RetainPtrArc"; +} bool isCheckedPtr(const std::string &Name) { return Name == "CheckedPtr" || Name == "CheckedRef"; @@ -157,7 +159,8 @@ bool isCtorOfCheckedPtr(const clang::FunctionDecl *F) { bool isCtorOfRetainPtr(const clang::FunctionDecl *F) { const std::string &FunctionName = safeGetName(F); return FunctionName == "RetainPtr" || FunctionName == "adoptNS" || - FunctionName == "adoptCF" || FunctionName == "retainPtr"; + FunctionName == "adoptCF" || FunctionName == "retainPtr" || + FunctionName == "RetainPtrArc" || FunctionName == "adoptNSArc"; } bool isCtorOfSafePtr(const clang::FunctionDecl *F) { @@ -190,7 +193,7 @@ bool isRefOrCheckedPtrType(const clang::QualType T) { } bool isRetainPtrType(const clang::QualType T) { - return isPtrOfType(T, [](auto Name) { return Name == "RetainPtr"; }); + return isPtrOfType(T, [](auto Name) { return isRetainPtr(Name); }); } bool isOwnerPtrType(const clang::QualType T) { @@ -374,7 +377,7 @@ std::optional isGetterOfSafePtr(const CXXMethodDecl *M) { method == "impl")) return true; - if (className == "RetainPtr" && method == "get") + if (isRetainPtr(className) && method == "get") return true; // Ref -> T conversion @@ -395,7 +398,7 @@ std::optional isGetterOfSafePtr(const CXXMethodDecl *M) { } } - if (className == "RetainPtr") { + if (isRetainPtr(className)) { if (auto *maybeRefToRawOperator = dyn_cast(M)) { auto QT = maybeRefToRawOperator->getConversionType(); auto *T = QT.getTypePtrOrNull(); @@ -429,7 +432,7 @@ bool isCheckedPtr(const CXXRecordDecl *R) { bool isRetainPtr(const CXXRecordDecl *R) { assert(R); if (auto *TmplR = R->getTemplateInstantiationPattern()) - return safeGetName(TmplR) == "RetainPtr"; + return isRetainPtr(safeGetName(TmplR)); return false; } diff --git a/clang/lib/StaticAnalyzer/Checkers/WebKit/RetainPtrCtorAdoptChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/WebKit/RetainPtrCtorAdoptChecker.cpp index df699d2c7f2fd..1575c4bbf4179 100644 --- a/clang/lib/StaticAnalyzer/Checkers/WebKit/RetainPtrCtorAdoptChecker.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/WebKit/RetainPtrCtorAdoptChecker.cpp @@ -71,7 +71,7 @@ class RetainPtrCtorAdoptChecker } bool TraverseClassTemplateDecl(ClassTemplateDecl *CTD) { - if (safeGetName(CTD) == "RetainPtr") + if (isRetainPtr(safeGetName(CTD))) return true; // Skip the contents of RetainPtr. return Base::TraverseClassTemplateDecl(CTD); } @@ -191,7 +191,7 @@ class RetainPtrCtorAdoptChecker if (!Cls) return; - if (safeGetName(Cls) != "RetainPtr" || !CE->getNumArgs()) + if (!isRetainPtr(safeGetName(Cls)) || !CE->getNumArgs()) return; // Ignore RetainPtr construction inside adoptNS, adoptCF, and retainPtr. @@ -320,12 +320,12 @@ class RetainPtrCtorAdoptChecker if (auto *CD = dyn_cast(MD)) { auto QT = CD->getConversionType().getCanonicalType(); auto *ResultType = QT.getTypePtrOrNull(); - if (safeGetName(Cls) == "RetainPtr" && ResultType && + if (isRetainPtr(safeGetName(Cls)) && ResultType && (ResultType->isPointerType() || ResultType->isReferenceType() || ResultType->isObjCObjectPointerType())) return IsOwnedResult::NotOwned; } - if (safeGetName(MD) == "leakRef" && safeGetName(Cls) == "RetainPtr") + if (safeGetName(MD) == "leakRef" && isRetainPtr(safeGetName(Cls))) return IsOwnedResult::Owned; } } diff --git a/clang/test/Analysis/Checkers/WebKit/objc-mock-types.h b/clang/test/Analysis/Checkers/WebKit/objc-mock-types.h index 51de81ac0f033..a4332df682060 100644 --- a/clang/test/Analysis/Checkers/WebKit/objc-mock-types.h +++ b/clang/test/Analysis/Checkers/WebKit/objc-mock-types.h @@ -17,6 +17,7 @@ typedef const struct CF_BRIDGED_TYPE(NSString) __CFString * CFStringRef; typedef const struct CF_BRIDGED_TYPE(NSArray) __CFArray * CFArrayRef; typedef struct CF_BRIDGED_MUTABLE_TYPE(NSMutableArray) __CFArray * CFMutableArrayRef; typedef struct CF_BRIDGED_MUTABLE_TYPE(CFRunLoopRef) __CFRunLoop * CFRunLoopRef; +typedef struct CF_BRIDGED_TYPE(id) CGImage *CGImageRef; #define NS_RETURNS_RETAINED __attribute__((ns_returns_retained)) #define CF_CONSUMED __attribute__((cf_consumed)) @@ -150,6 +151,10 @@ namespace WTF { void WTFCrash(void); +#if __has_feature(objc_arc) +#define RetainPtr RetainPtrArc +#endif + template class RetainPtr; template RetainPtr adoptNS(T*); template RetainPtr adoptCF(T); diff --git a/clang/test/Analysis/Checkers/WebKit/unretained-call-args-arc.mm b/clang/test/Analysis/Checkers/WebKit/unretained-call-args-arc.mm index f1f4d912663aa..4207c1836079f 100644 --- a/clang/test/Analysis/Checkers/WebKit/unretained-call-args-arc.mm +++ b/clang/test/Analysis/Checkers/WebKit/unretained-call-args-arc.mm @@ -5,6 +5,8 @@ SomeObj *provide(); CFMutableArrayRef provide_cf(); void someFunction(); +CGImageRef provideImage(); +NSString *stringForImage(CGImageRef); namespace raw_ptr { @@ -36,4 +38,13 @@ - (SomeObj *)getSomeObj { - (void)doWorkOnSomeObj { [[self getSomeObj] doWork]; } + +- (CGImageRef)createImage { + return provideImage(); +} + +- (NSString *)convertImage { + RetainPtr image = [self createImage]; + return stringForImage(image.get()); +} @end diff --git a/clang/test/Analysis/Checkers/WebKit/unretained-call-args.mm b/clang/test/Analysis/Checkers/WebKit/unretained-call-args.mm index 263514a87896e..5215c80801d72 100644 --- a/clang/test/Analysis/Checkers/WebKit/unretained-call-args.mm +++ b/clang/test/Analysis/Checkers/WebKit/unretained-call-args.mm @@ -8,6 +8,9 @@ CFMutableArrayRef provide_cf(); void consume_cf(CFMutableArrayRef); +CGImageRef provideImage(); +NSString *stringForImage(CGImageRef); + void some_function(); namespace simple { @@ -439,4 +442,12 @@ - (void)doWorkOnSomeObj { [[self getSomeObj] doWork]; } +- (CGImageRef)createImage { + return provideImage(); +} + +- (NSString *)convertImage { + RetainPtr image = [self createImage]; + return stringForImage(image.get()); +} @end From 3803f3b720c38111c1c29e9ce5d95579a43d68e5 Mon Sep 17 00:00:00 2001 From: Ryosuke Niwa Date: Tue, 22 Apr 2025 16:15:26 -0700 Subject: [PATCH 28/31] [WebKit checkers] Treat global const variables as safe (#136170) This PR makes WebKit checkers treat a variable with global storage as safe instead of constraining to ones that start with k or _k. --- .../Checkers/WebKit/ASTUtils.cpp | 11 +++++----- .../WebKit/unretained-call-args-arc.mm | 18 ++++++++++++++++ .../Checkers/WebKit/unretained-call-args.mm | 19 +++++++++++++++++ .../WebKit/unretained-local-vars-arc.mm | 20 ++++++++++++++++++ .../Checkers/WebKit/unretained-local-vars.mm | 21 +++++++++++++++++++ 5 files changed, 83 insertions(+), 6 deletions(-) diff --git a/clang/lib/StaticAnalyzer/Checkers/WebKit/ASTUtils.cpp b/clang/lib/StaticAnalyzer/Checkers/WebKit/ASTUtils.cpp index c36c925c0d2d2..88b98756b2ca8 100644 --- a/clang/lib/StaticAnalyzer/Checkers/WebKit/ASTUtils.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/WebKit/ASTUtils.cpp @@ -30,12 +30,11 @@ bool tryToFindPtrOrigin( std::function callback) { while (E) { if (auto *DRE = dyn_cast(E)) { - auto *ValDecl = DRE->getDecl(); - auto QT = ValDecl->getType(); - auto ValName = ValDecl->getName(); - if (ValDecl && (ValName.starts_with('k') || ValName.starts_with("_k")) && - QT.isConstQualified()) { // Treat constants such as kCF* as safe. - return callback(E, true); + if (auto *VD = dyn_cast_or_null(DRE->getDecl())) { + auto QT = VD->getType(); + if (VD->hasGlobalStorage() && QT.isConstQualified()) { + return callback(E, true); + } } } if (auto *tempExpr = dyn_cast(E)) { diff --git a/clang/test/Analysis/Checkers/WebKit/unretained-call-args-arc.mm b/clang/test/Analysis/Checkers/WebKit/unretained-call-args-arc.mm index 4207c1836079f..fa866258a2f6d 100644 --- a/clang/test/Analysis/Checkers/WebKit/unretained-call-args-arc.mm +++ b/clang/test/Analysis/Checkers/WebKit/unretained-call-args-arc.mm @@ -18,6 +18,24 @@ void foo() { } // namespace raw_ptr +namespace const_global { + +extern NSString * const SomeConstant; +extern CFDictionaryRef const SomeDictionary; +void doWork(NSString *str, CFDictionaryRef dict); +void use_const_global() { + doWork(SomeConstant, SomeDictionary); +} + +NSString *provide_str(); +CFDictionaryRef provide_dict(); +void use_const_local() { + doWork(provide_str(), provide_dict()); + // expected-warning@-1{{Call argument for parameter 'dict' is unretained and unsafe}} +} + +} // namespace const_global + @interface AnotherObj : NSObject - (void)foo:(SomeObj *)obj; - (SomeObj *)getSomeObj; diff --git a/clang/test/Analysis/Checkers/WebKit/unretained-call-args.mm b/clang/test/Analysis/Checkers/WebKit/unretained-call-args.mm index 5215c80801d72..ed4783f70f678 100644 --- a/clang/test/Analysis/Checkers/WebKit/unretained-call-args.mm +++ b/clang/test/Analysis/Checkers/WebKit/unretained-call-args.mm @@ -414,6 +414,25 @@ void idcf(CFTypeRef obj) { } // ptr_conversion +namespace const_global { + +extern NSString * const SomeConstant; +extern CFDictionaryRef const SomeDictionary; +void doWork(NSString *str, CFDictionaryRef dict); +void use_const_global() { + doWork(SomeConstant, SomeDictionary); +} + +NSString *provide_str(); +CFDictionaryRef provide_dict(); +void use_const_local() { + doWork(provide_str(), provide_dict()); + // expected-warning@-1{{Call argument for parameter 'str' is unretained and unsafe}} + // expected-warning@-2{{Call argument for parameter 'dict' is unretained and unsafe}} +} + +} // namespace const_global + @interface TestObject : NSObject - (void)doWork:(NSString *)msg, ...; - (void)doWorkOnSelf; diff --git a/clang/test/Analysis/Checkers/WebKit/unretained-local-vars-arc.mm b/clang/test/Analysis/Checkers/WebKit/unretained-local-vars-arc.mm index 92a718f7e3a4c..a84bee8529645 100644 --- a/clang/test/Analysis/Checkers/WebKit/unretained-local-vars-arc.mm +++ b/clang/test/Analysis/Checkers/WebKit/unretained-local-vars-arc.mm @@ -25,6 +25,26 @@ void bar() { } // namespace raw_ptr +namespace const_global { + +extern NSString * const SomeConstant; +extern CFDictionaryRef const SomeDictionary; +void doWork(NSString *, CFDictionaryRef); +void use_const_global() { + doWork(SomeConstant, SomeDictionary); +} + +NSString *provide_str(); +CFDictionaryRef provide_dict(); +void use_const_local() { + NSString * const str = provide_str(); + CFDictionaryRef dict = provide_dict(); + // expected-warning@-1{{Local variable 'dict' is unretained and unsafe [alpha.webkit.UnretainedLocalVarsChecker]}} + doWork(str, dict); +} + +} // namespace const_global + @interface AnotherObj : NSObject - (void)foo:(SomeObj *)obj; @end diff --git a/clang/test/Analysis/Checkers/WebKit/unretained-local-vars.mm b/clang/test/Analysis/Checkers/WebKit/unretained-local-vars.mm index a71a80ea3d647..10f7c9acb7a3c 100644 --- a/clang/test/Analysis/Checkers/WebKit/unretained-local-vars.mm +++ b/clang/test/Analysis/Checkers/WebKit/unretained-local-vars.mm @@ -387,6 +387,27 @@ unsigned ccf(CFTypeRef obj) { } // ptr_conversion +namespace const_global { + +extern NSString * const SomeConstant; +extern CFDictionaryRef const SomeDictionary; +void doWork(NSString *, CFDictionaryRef); +void use_const_global() { + doWork(SomeConstant, SomeDictionary); +} + +NSString *provide_str(); +CFDictionaryRef provide_dict(); +void use_const_local() { + NSString * const str = provide_str(); + // expected-warning@-1{{Local variable 'str' is unretained and unsafe [alpha.webkit.UnretainedLocalVarsChecker]}} + CFDictionaryRef dict = provide_dict(); + // expected-warning@-1{{Local variable 'dict' is unretained and unsafe [alpha.webkit.UnretainedLocalVarsChecker]}} + doWork(str, dict); +} + +} // namespace const_global + bool doMoreWorkOpaque(OtherObj*); SomeObj* provide(); From daeb3ec5c9bb9aab6f9efa74e670c59d1fbd743e Mon Sep 17 00:00:00 2001 From: Ryosuke Niwa Date: Wed, 23 Apr 2025 12:41:56 -0700 Subject: [PATCH 29/31] [RawPtrRefMemberChecker] Member variable checker should allow T* in smart pointer classes (#136503) This PR fixes member variable checker to allow the usage of T* in smart pointer classes. e.g. alpha.webkit.NoUncheckedPtrMemberChecker should allow T* to appear within RefPtr. --- .../Checkers/WebKit/PtrTypesSemantics.cpp | 7 +++++++ .../Checkers/WebKit/PtrTypesSemantics.h | 4 ++++ .../Checkers/WebKit/RawPtrRefMemberChecker.cpp | 17 ++--------------- .../Checkers/WebKit/unchecked-members.cpp | 9 +++++++++ .../Checkers/WebKit/uncounted-members.cpp | 15 ++++++++++++--- 5 files changed, 34 insertions(+), 18 deletions(-) diff --git a/clang/lib/StaticAnalyzer/Checkers/WebKit/PtrTypesSemantics.cpp b/clang/lib/StaticAnalyzer/Checkers/WebKit/PtrTypesSemantics.cpp index 811888e119449..ba0c7fd77b410 100644 --- a/clang/lib/StaticAnalyzer/Checkers/WebKit/PtrTypesSemantics.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/WebKit/PtrTypesSemantics.cpp @@ -436,6 +436,13 @@ bool isRetainPtr(const CXXRecordDecl *R) { return false; } +bool isSmartPtr(const CXXRecordDecl *R) { + assert(R); + if (auto *TmplR = R->getTemplateInstantiationPattern()) + return isSmartPtrClass(safeGetName(TmplR)); + return false; +} + bool isPtrConversion(const FunctionDecl *F) { assert(F); if (isCtorOfRefCounted(F)) diff --git a/clang/lib/StaticAnalyzer/Checkers/WebKit/PtrTypesSemantics.h b/clang/lib/StaticAnalyzer/Checkers/WebKit/PtrTypesSemantics.h index 97c9d0510e67d..f9fcfe9878d54 100644 --- a/clang/lib/StaticAnalyzer/Checkers/WebKit/PtrTypesSemantics.h +++ b/clang/lib/StaticAnalyzer/Checkers/WebKit/PtrTypesSemantics.h @@ -58,6 +58,10 @@ bool isCheckedPtr(const clang::CXXRecordDecl *Class); /// \returns true if \p Class is a RetainPtr, false if not. bool isRetainPtr(const clang::CXXRecordDecl *Class); +/// \returns true if \p Class is a smart pointer (RefPtr, WeakPtr, etc...), +/// false if not. +bool isSmartPtr(const clang::CXXRecordDecl *Class); + /// \returns true if \p Class is ref-countable AND not ref-counted, false if /// not, std::nullopt if inconclusive. std::optional isUncounted(const clang::QualType T); diff --git a/clang/lib/StaticAnalyzer/Checkers/WebKit/RawPtrRefMemberChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/WebKit/RawPtrRefMemberChecker.cpp index 4dd2bf578da6b..245710f1aee71 100644 --- a/clang/lib/StaticAnalyzer/Checkers/WebKit/RawPtrRefMemberChecker.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/WebKit/RawPtrRefMemberChecker.cpp @@ -41,7 +41,6 @@ class RawPtrRefMemberChecker virtual std::optional isPtrCompatible(const clang::QualType, const clang::CXXRecordDecl *R) const = 0; - virtual bool isPtrCls(const clang::CXXRecordDecl *) const = 0; virtual const char *typeName() const = 0; virtual const char *invariant() const = 0; @@ -206,8 +205,8 @@ class RawPtrRefMemberChecker // Ref-counted smartpointers actually have raw-pointer to uncounted type as // a member but we trust them to handle it correctly. auto CXXRD = llvm::dyn_cast_or_null(RD); - if (CXXRD) - return isPtrCls(CXXRD); + if (CXXRD && isSmartPtr(CXXRD)) + return true; return false; } @@ -271,10 +270,6 @@ class NoUncountedMemberChecker final : public RawPtrRefMemberChecker { return R ? isRefCountable(R) : std::nullopt; } - bool isPtrCls(const clang::CXXRecordDecl *R) const final { - return isRefCounted(R); - } - const char *typeName() const final { return "ref-countable type"; } const char *invariant() const final { @@ -294,10 +289,6 @@ class NoUncheckedPtrMemberChecker final : public RawPtrRefMemberChecker { return R ? isCheckedPtrCapable(R) : std::nullopt; } - bool isPtrCls(const clang::CXXRecordDecl *R) const final { - return isCheckedPtr(R); - } - const char *typeName() const final { return "CheckedPtr capable type"; } const char *invariant() const final { @@ -320,10 +311,6 @@ class NoUnretainedMemberChecker final : public RawPtrRefMemberChecker { return RTC->isUnretained(QT); } - bool isPtrCls(const clang::CXXRecordDecl *R) const final { - return isRetainPtr(R); - } - const char *typeName() const final { return "retainable type"; } const char *invariant() const final { diff --git a/clang/test/Analysis/Checkers/WebKit/unchecked-members.cpp b/clang/test/Analysis/Checkers/WebKit/unchecked-members.cpp index 0189b0cd50fcc..048ffbffcdefb 100644 --- a/clang/test/Analysis/Checkers/WebKit/unchecked-members.cpp +++ b/clang/test/Analysis/Checkers/WebKit/unchecked-members.cpp @@ -50,3 +50,12 @@ namespace ignore_unions { void forceTmplToInstantiate(FooTmpl) { } } // namespace ignore_unions + +namespace checked_ptr_ref_ptr_capable { + + RefCountableAndCheckable* provide(); + void foo() { + RefPtr foo = provide(); + } + +} // checked_ptr_ref_ptr_capable diff --git a/clang/test/Analysis/Checkers/WebKit/uncounted-members.cpp b/clang/test/Analysis/Checkers/WebKit/uncounted-members.cpp index 1bdbaedefbfeb..130777a9a5fee 100644 --- a/clang/test/Analysis/Checkers/WebKit/uncounted-members.cpp +++ b/clang/test/Analysis/Checkers/WebKit/uncounted-members.cpp @@ -34,7 +34,7 @@ namespace members { private: RefCountable* a = nullptr; }; -} +} // members namespace ignore_unions { union Foo { @@ -49,7 +49,7 @@ namespace ignore_unions { }; void forceTmplToInstantiate(RefPtr) {} -} +} // ignore_unions namespace ignore_system_header { @@ -67,4 +67,13 @@ namespace ignore_non_ref_countable { struct Bar { Foo* foo; }; -} \ No newline at end of file +} // ignore_non_ref_countable + +namespace checked_ptr_ref_ptr_capable { + + RefCountableAndCheckable* provide(); + void foo() { + CheckedPtr foo = provide(); + } + +} // checked_ptr_ref_ptr_capable From 56115871d5f8d24817dcd4feb58a00f468057223 Mon Sep 17 00:00:00 2001 From: Ryosuke Niwa Date: Thu, 24 Apr 2025 13:15:27 -0700 Subject: [PATCH 30/31] [alpha.webkit.UncheckedCallArgsChecker] Checker fails to recognize CanMakeCheckedPtrBase (#136500) This PR fixes the bug that alpha.webkit.UncheckedCallArgsChecker did not recognize CanMakeCheckedPtrBase due to getAsCXXRecordDecl returning nullptr for it in hasPublicMethodInBase. Manually grab getTemplatedDecl out of TemplateSpecializationType then CXXRecordDecl to workaround this bug in clang frontend. --- .../Checkers/WebKit/PtrTypesSemantics.cpp | 14 ++++++-- .../Checkers/WebKit/unchecked-call-arg.cpp | 34 +++++++++++++++++++ 2 files changed, 46 insertions(+), 2 deletions(-) create mode 100644 clang/test/Analysis/Checkers/WebKit/unchecked-call-arg.cpp diff --git a/clang/lib/StaticAnalyzer/Checkers/WebKit/PtrTypesSemantics.cpp b/clang/lib/StaticAnalyzer/Checkers/WebKit/PtrTypesSemantics.cpp index ba0c7fd77b410..d7111bcb35115 100644 --- a/clang/lib/StaticAnalyzer/Checkers/WebKit/PtrTypesSemantics.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/WebKit/PtrTypesSemantics.cpp @@ -46,8 +46,18 @@ hasPublicMethodInBase(const CXXBaseSpecifier *Base, StringRef NameToMatch) { return std::nullopt; const CXXRecordDecl *R = T->getAsCXXRecordDecl(); - if (!R) - return std::nullopt; + if (!R) { + auto CT = Base->getType().getCanonicalType(); + if (auto *TST = dyn_cast(CT)) { + auto TmplName = TST->getTemplateName(); + if (!TmplName.isNull()) { + if (auto *TD = TmplName.getAsTemplateDecl()) + R = dyn_cast_or_null(TD->getTemplatedDecl()); + } + } + if (!R) + return std::nullopt; + } if (!R->hasDefinition()) return std::nullopt; diff --git a/clang/test/Analysis/Checkers/WebKit/unchecked-call-arg.cpp b/clang/test/Analysis/Checkers/WebKit/unchecked-call-arg.cpp new file mode 100644 index 0000000000000..8685978ebf1ac --- /dev/null +++ b/clang/test/Analysis/Checkers/WebKit/unchecked-call-arg.cpp @@ -0,0 +1,34 @@ +// RUN: %clang_analyze_cc1 -analyzer-checker=alpha.webkit.UncheckedCallArgsChecker -verify %s + +void WTFCrash(void); + +enum class Tag : bool { Value }; + +template class CanMakeCheckedPtrBase { +public: + void incrementCheckedPtrCount() const { ++m_checkedPtrCount; } + inline void decrementCheckedPtrCount() const + { + if (!m_checkedPtrCount) + WTFCrash(); + --m_checkedPtrCount; + } + +private: + mutable StorageType m_checkedPtrCount { 0 }; +}; + +template +class CanMakeCheckedPtr : public CanMakeCheckedPtrBase { +}; + +class CheckedObject : public CanMakeCheckedPtr { +public: + void doWork(); +}; + +CheckedObject* provide(); +void foo() { + provide()->doWork(); + // expected-warning@-1{{Call argument for 'this' parameter is unchecked and unsafe}} +} From 41f92b62da5e3abf38f961362d3ba3f2fb54b96f Mon Sep 17 00:00:00 2001 From: Ryosuke Niwa Date: Thu, 24 Apr 2025 17:10:05 -0700 Subject: [PATCH 31/31] [webkit.UncountedLambdaCapturesChecker] Treat a call to lambda function via a variable as safe. (#135688) This PR makes the checker ignore a function call to lambda via a local variable. --- .../WebKit/RawPtrRefLambdaCapturesChecker.cpp | 2 -- .../Checkers/WebKit/uncounted-lambda-captures.cpp | 11 +++++++++++ 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/clang/lib/StaticAnalyzer/Checkers/WebKit/RawPtrRefLambdaCapturesChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/WebKit/RawPtrRefLambdaCapturesChecker.cpp index a7492170d0ec2..bca734c196e72 100644 --- a/clang/lib/StaticAnalyzer/Checkers/WebKit/RawPtrRefLambdaCapturesChecker.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/WebKit/RawPtrRefLambdaCapturesChecker.cpp @@ -266,8 +266,6 @@ class RawPtrRefLambdaCapturesChecker return; DeclRefExprsToIgnore.insert(ArgRef); LambdasToIgnore.insert(L); - Checker->visitLambdaExpr(L, shouldCheckThis() && !hasProtectedThis(L), - ClsType, /* ignoreParamVarDecl */ true); } bool hasProtectedThis(LambdaExpr *L) { diff --git a/clang/test/Analysis/Checkers/WebKit/uncounted-lambda-captures.cpp b/clang/test/Analysis/Checkers/WebKit/uncounted-lambda-captures.cpp index 36135973e78c0..daa15d55aee5a 100644 --- a/clang/test/Analysis/Checkers/WebKit/uncounted-lambda-captures.cpp +++ b/clang/test/Analysis/Checkers/WebKit/uncounted-lambda-captures.cpp @@ -372,6 +372,17 @@ void trivial_lambda() { trivial_lambda(); } +bool call_lambda_var_decl() { + RefCountable* ref_countable = make_obj(); + auto lambda1 = [&]() -> bool { + return ref_countable->next(); + }; + auto lambda2 = [=]() -> bool { + return ref_countable->next(); + }; + return lambda1() && lambda2(); +} + void lambda_with_args(RefCountable* obj) { auto trivial_lambda = [&](int v) { obj->method();