Skip to content

[alpha.webkit.UnretainedLocalVarsChecker] Add a checker for local variables to NS and CF types. #127554

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 7 commits into from
Feb 24, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 45 additions & 0 deletions clang/docs/analyzer/checkers.rst
Original file line number Diff line number Diff line change
Expand Up @@ -3668,6 +3668,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<NSObject> retained;
// The scope of unretained is EMBEDDED in the scope of retained.
{
NSObject* unretained = retained.get(); // ok
}
}

void foo2(RetainPtr<NSObject> 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<NSObject> retained;
// The scope of unretained is not EMBEDDED in the scope of retained.
NSObject* unretained = retained.get(); // warn
}

Debug Checkers
---------------

Expand Down
4 changes: 4 additions & 0 deletions clang/include/clang/StaticAnalyzer/Checkers/Checkers.td
Original file line number Diff line number Diff line change
Expand Up @@ -1782,4 +1782,8 @@ def UncheckedLocalVarsChecker : Checker<"UncheckedLocalVarsChecker">,
HelpText<"Check unchecked local variables.">,
Documentation<HasDocumentation>;

def UnretainedLocalVarsChecker : Checker<"UnretainedLocalVarsChecker">,
HelpText<"Check unretained local variables.">,
Documentation<HasDocumentation>;

} // end alpha.webkit
8 changes: 5 additions & 3 deletions clang/lib/StaticAnalyzer/Checkers/WebKit/ASTUtils.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ bool isSafePtr(clang::CXXRecordDecl *Decl) {

bool tryToFindPtrOrigin(
const Expr *E, bool StopAtFirstRefCountedObj,
std::function<bool(const clang::CXXRecordDecl *)> isSafePtr,
std::function<bool(const clang::QualType)> isSafePtrType,
std::function<bool(const clang::Expr *, bool)> callback) {
while (E) {
if (auto *tempExpr = dyn_cast<MaterializeTemporaryExpr>(E)) {
Expand Down Expand Up @@ -53,9 +55,9 @@ bool tryToFindPtrOrigin(
}
if (auto *Expr = dyn_cast<ConditionalOperator>(E)) {
return tryToFindPtrOrigin(Expr->getTrueExpr(), StopAtFirstRefCountedObj,
callback) &&
isSafePtr, isSafePtrType, callback) &&
tryToFindPtrOrigin(Expr->getFalseExpr(), StopAtFirstRefCountedObj,
callback);
isSafePtr, isSafePtrType, callback);
}
if (auto *cast = dyn_cast<CastExpr>(E)) {
if (StopAtFirstRefCountedObj) {
Expand Down Expand Up @@ -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);

Expand Down
2 changes: 2 additions & 0 deletions clang/lib/StaticAnalyzer/Checkers/WebKit/ASTUtils.h
Original file line number Diff line number Diff line change
Expand Up @@ -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<bool(const clang::CXXRecordDecl *)> isSafePtr,
std::function<bool(const clang::QualType)> isSafePtrType,
std::function<bool(const clang::Expr *, bool)> callback);

/// For \p E referring to a ref-countable/-counted pointer/reference we return
Expand Down
129 changes: 118 additions & 11 deletions clang/lib/StaticAnalyzer/Checkers/WebKit/PtrTypesSemantics.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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 <optional>

using namespace clang;
Expand Down Expand Up @@ -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";
}
Expand All @@ -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 <typename Predicate>
Expand All @@ -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" ||
Expand Down Expand Up @@ -195,6 +209,69 @@ std::optional<bool> 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<RecordType>();
if (!RT)
return;

for (auto *Redecl : RT->getDecl()->getMostRecentDecl()->redecls()) {
if (Redecl->getAttr<ObjCBridgeAttr>()) {
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<RecordType>(PointeeType.getTypePtrOrNull());
return RT && CFPointees.contains(RT);
}

std::optional<bool> isUnretained(const QualType T, bool IsARCEnabled) {
if (auto *Subst = dyn_cast<SubstTemplateTypeParmType>(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<bool> isUncounted(const CXXRecordDecl* Class)
{
// Keep isRefCounted first as it's cheaper.
Expand Down Expand Up @@ -230,16 +307,20 @@ std::optional<bool> isUncheckedPtr(const QualType T) {
return false;
}

std::optional<bool> isUnsafePtr(const QualType T) {
std::optional<bool> 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<bool> 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;
Expand All @@ -248,6 +329,8 @@ std::optional<bool> isUnsafePtr(const QualType T) {
std::optional<bool> isGetterOfSafePtr(const CXXMethodDecl *M) {
assert(M);

std::optional<RetainTypeChecker> RTC;

if (isa<CXXMethodDecl>(M)) {
const CXXRecordDecl *calleeMethodsClass = M->getParent();
auto className = safeGetName(calleeMethodsClass);
Expand All @@ -263,16 +346,33 @@ std::optional<bool> isGetterOfSafePtr(const CXXMethodDecl *M) {
method == "impl"))
return true;

if (className == "RetainPtr" && method == "get")
return true;

// Ref<T> -> T conversion
// FIXME: Currently allowing any Ref<T> -> whatever cast.
if (isRefType(className)) {
if (auto *maybeRefToRawOperator = dyn_cast<CXXConversionDecl>(M))
return isUnsafePtr(maybeRefToRawOperator->getConversionType());
if (auto *maybeRefToRawOperator = dyn_cast<CXXConversionDecl>(M)) {
auto QT = maybeRefToRawOperator->getConversionType();
auto *T = QT.getTypePtrOrNull();
return T && (T->isPointerType() || T->isReferenceType());
}
}

if (isCheckedPtr(className)) {
if (auto *maybeRefToRawOperator = dyn_cast<CXXConversionDecl>(M))
return isUnsafePtr(maybeRefToRawOperator->getConversionType());
if (auto *maybeRefToRawOperator = dyn_cast<CXXConversionDecl>(M)) {
auto QT = maybeRefToRawOperator->getConversionType();
auto *T = QT.getTypePtrOrNull();
return T && (T->isPointerType() || T->isReferenceType());
}
}

if (className == "RetainPtr") {
if (auto *maybeRefToRawOperator = dyn_cast<CXXConversionDecl>(M)) {
auto QT = maybeRefToRawOperator->getConversionType();
auto *T = QT.getTypePtrOrNull();
return T && (T->isPointerType() || T->isReferenceType());
}
}
}
return false;
Expand All @@ -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))
Expand Down
30 changes: 28 additions & 2 deletions clang/lib/StaticAnalyzer/Checkers/WebKit/PtrTypesSemantics.h
Original file line number Diff line number Diff line change
Expand Up @@ -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 <optional>

Expand All @@ -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<T> and RefPtr<T>
// implementation. It can be modeled as: type T having public methods ref() and
Expand Down Expand Up @@ -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<bool> isUncounted(const clang::QualType T);
Expand All @@ -59,6 +66,22 @@ std::optional<bool> isUncounted(const clang::QualType T);
/// not, std::nullopt if inconclusive.
std::optional<bool> isUnchecked(const clang::QualType T);

/// An inter-procedural analysis facility that detects CF types with the
/// underlying pointer type.
class RetainTypeChecker {
llvm::DenseSet<const RecordType *> 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<bool> 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<bool> isUncounted(const clang::CXXRecordDecl* Class);
Expand All @@ -77,11 +100,14 @@ std::optional<bool> 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<bool> isUnsafePtr(const QualType T);
std::optional<bool> 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.
Expand Down
Loading