Skip to content

Commit 90403f7

Browse files
committed
[alpha.webkit.UnretainedCallArgsChecker] Add a checker for NS or CF type call arguments.
This PR adds alpha.webkit.UnretainedCallArgsChecker by generalizing RawPtrRefLocalVarsChecker. It checks call arguments of NS or CF types are backed by 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.
1 parent b335d5a commit 90403f7

File tree

8 files changed

+584
-20
lines changed

8 files changed

+584
-20
lines changed

clang/docs/analyzer/checkers.rst

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3582,6 +3582,12 @@ The goal of this rule is to make sure that lifetime of any dynamically allocated
35823582
35833583
The rules of when to use and not to use CheckedPtr / CheckedRef are same as alpha.webkit.UncountedCallArgsChecker for ref-counted objects.
35843584
3585+
alpha.webkit.UnretainedCallArgsChecker
3586+
"""""""""""""""""""""""""""""""""""""
3587+
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.
3588+
3589+
The rules of when to use and not to use RetainPtr are same as alpha.webkit.UncountedCallArgsChecker for ref-counted objects.
3590+
35853591
alpha.webkit.UncountedLocalVarsChecker
35863592
""""""""""""""""""""""""""""""""""""""
35873593
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.

clang/include/clang/StaticAnalyzer/Checkers/Checkers.td

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1774,6 +1774,10 @@ def UncheckedCallArgsChecker : Checker<"UncheckedCallArgsChecker">,
17741774
HelpText<"Check unchecked call arguments.">,
17751775
Documentation<HasDocumentation>;
17761776

1777+
def UnretainedCallArgsChecker : Checker<"UnretainedCallArgsChecker">,
1778+
HelpText<"Check unretained call arguments.">,
1779+
Documentation<HasDocumentation>;
1780+
17771781
def UncountedLocalVarsChecker : Checker<"UncountedLocalVarsChecker">,
17781782
HelpText<"Check uncounted local variables.">,
17791783
Documentation<HasDocumentation>;

clang/lib/StaticAnalyzer/Checkers/WebKit/ASTUtils.cpp

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88

99
#include "ASTUtils.h"
1010
#include "PtrTypesSemantics.h"
11+
#include "clang/AST/Attr.h"
1112
#include "clang/AST/CXXInheritance.h"
1213
#include "clang/AST/Decl.h"
1314
#include "clang/AST/DeclCXX.h"
@@ -28,6 +29,15 @@ bool tryToFindPtrOrigin(
2829
std::function<bool(const clang::QualType)> isSafePtrType,
2930
std::function<bool(const clang::Expr *, bool)> callback) {
3031
while (E) {
32+
if (auto *DRE = dyn_cast<DeclRefExpr>(E)) {
33+
auto *ValDecl = DRE->getDecl();
34+
auto QT = ValDecl->getType();
35+
auto ValName = ValDecl->getName();
36+
if (ValDecl && (ValName.starts_with('k') || ValName.starts_with("_k")) &&
37+
QT.isConstQualified()) {
38+
return callback(E, true);
39+
}
40+
}
3141
if (auto *tempExpr = dyn_cast<MaterializeTemporaryExpr>(E)) {
3242
E = tempExpr->getSubExpr();
3343
continue;
@@ -117,6 +127,9 @@ bool tryToFindPtrOrigin(
117127
E = call->getArg(0);
118128
continue;
119129
}
130+
131+
if (safeGetName(callee) == "__builtin___CFStringMakeConstantString")
132+
return callback(E, true);
120133
}
121134
}
122135
if (auto *ObjCMsgExpr = dyn_cast<ObjCMessageExpr>(E)) {

clang/lib/StaticAnalyzer/Checkers/WebKit/PtrTypesSemantics.cpp

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -371,7 +371,8 @@ std::optional<bool> isGetterOfSafePtr(const CXXMethodDecl *M) {
371371
if (auto *maybeRefToRawOperator = dyn_cast<CXXConversionDecl>(M)) {
372372
auto QT = maybeRefToRawOperator->getConversionType();
373373
auto *T = QT.getTypePtrOrNull();
374-
return T && (T->isPointerType() || T->isReferenceType());
374+
return T && (T->isPointerType() || T->isReferenceType() ||
375+
T->isObjCObjectPointerType());
375376
}
376377
}
377378
}
@@ -414,7 +415,8 @@ bool isPtrConversion(const FunctionDecl *F) {
414415
if (FunctionName == "getPtr" || FunctionName == "WeakPtr" ||
415416
FunctionName == "dynamicDowncast" || FunctionName == "downcast" ||
416417
FunctionName == "checkedDowncast" ||
417-
FunctionName == "uncheckedDowncast" || FunctionName == "bitwise_cast")
418+
FunctionName == "uncheckedDowncast" || FunctionName == "bitwise_cast" ||
419+
FunctionName == "bridge_cast")
418420
return true;
419421

420422
return false;

clang/lib/StaticAnalyzer/Checkers/WebKit/RawPtrRefCallArgsChecker.cpp

Lines changed: 110 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
#include "clang/AST/Decl.h"
1414
#include "clang/AST/DeclCXX.h"
1515
#include "clang/AST/DynamicRecursiveASTVisitor.h"
16+
#include "clang/Analysis/DomainSpecific/CocoaConventions.h"
1617
#include "clang/Basic/SourceLocation.h"
1718
#include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h"
1819
#include "clang/StaticAnalyzer/Core/BugReporter/BugReporter.h"
@@ -35,6 +36,9 @@ class RawPtrRefCallArgsChecker
3536
TrivialFunctionAnalysis TFA;
3637
EnsureFunctionAnalysis EFA;
3738

39+
protected:
40+
mutable std::optional<RetainTypeChecker> RTC;
41+
3842
public:
3943
RawPtrRefCallArgsChecker(const char *description)
4044
: Bug(this, description, "WebKit coding guidelines") {}
@@ -80,9 +84,22 @@ class RawPtrRefCallArgsChecker
8084
Checker->visitCallExpr(CE, DeclWithIssue);
8185
return true;
8286
}
87+
88+
bool VisitTypedefDecl(TypedefDecl *TD) override {
89+
if (Checker->RTC)
90+
Checker->RTC->visitTypedef(TD);
91+
return true;
92+
}
93+
94+
bool VisitObjCMessageExpr(ObjCMessageExpr *ObjCMsgExpr) override {
95+
Checker->visitObjCMessageExpr(ObjCMsgExpr, DeclWithIssue);
96+
return true;
97+
}
8398
};
8499

85100
LocalVisitor visitor(this);
101+
if (RTC)
102+
RTC->visitTranslationUnitDecl(TUD);
86103
visitor.TraverseDecl(const_cast<TranslationUnitDecl *>(TUD));
87104
}
88105

@@ -122,7 +139,7 @@ class RawPtrRefCallArgsChecker
122139
// if ((*P)->hasAttr<SafeRefCntblRawPtrAttr>())
123140
// continue;
124141

125-
QualType ArgType = (*P)->getType().getCanonicalType();
142+
QualType ArgType = (*P)->getType();
126143
// FIXME: more complex types (arrays, references to raw pointers, etc)
127144
std::optional<bool> IsUncounted = isUnsafePtr(ArgType);
128145
if (!IsUncounted || !(*IsUncounted))
@@ -141,6 +158,42 @@ class RawPtrRefCallArgsChecker
141158
}
142159
}
143160

161+
void visitObjCMessageExpr(const ObjCMessageExpr *E, const Decl *D) const {
162+
if (BR->getSourceManager().isInSystemHeader(E->getExprLoc()))
163+
return;
164+
165+
auto Selector = E->getSelector();
166+
if (auto *Receiver = E->getInstanceReceiver()->IgnoreParenCasts()) {
167+
std::optional<bool> IsUnsafe = isUnsafePtr(E->getReceiverType());
168+
if (IsUnsafe && *IsUnsafe && !isPtrOriginSafe(Receiver)) {
169+
if (auto *InnerMsg = dyn_cast<ObjCMessageExpr>(Receiver)) {
170+
auto InnerSelector = InnerMsg->getSelector();
171+
if (InnerSelector.getNameForSlot(0) == "alloc"
172+
&& Selector.getNameForSlot(0).starts_with("init"))
173+
return;
174+
}
175+
reportBugOnReceiver(Receiver, D);
176+
}
177+
}
178+
179+
auto *MethodDecl = E->getMethodDecl();
180+
if (!MethodDecl)
181+
return;
182+
183+
auto ArgCount = E->getNumArgs();
184+
for (unsigned i = 0; i < ArgCount && i < MethodDecl->param_size(); ++i) {
185+
auto *Arg = E->getArg(i);
186+
auto *Param = MethodDecl->getParamDecl(i);
187+
auto ArgType = Param->getType();
188+
std::optional<bool> IsUnsafe = isUnsafePtr(ArgType);
189+
if (!IsUnsafe || !(*IsUnsafe))
190+
continue;
191+
if (isPtrOriginSafe(Arg))
192+
continue;
193+
reportBug(Arg, Param, D);
194+
}
195+
}
196+
144197
bool isPtrOriginSafe(const Expr *Arg) const {
145198
return tryToFindPtrOrigin(
146199
Arg, /*StopAtFirstRefCountedObj=*/true,
@@ -158,6 +211,8 @@ class RawPtrRefCallArgsChecker
158211
// foo(NULL)
159212
return true;
160213
}
214+
if (isa<ObjCStringLiteral>(ArgOrigin))
215+
return true;
161216
if (isASafeCallArg(ArgOrigin))
162217
return true;
163218
if (EFA.isACallToEnsureFn(ArgOrigin))
@@ -212,7 +267,7 @@ class RawPtrRefCallArgsChecker
212267
overloadedOperatorType == OO_PipePipe)
213268
return true;
214269

215-
if (isCtorOfRefCounted(Callee))
270+
if (isCtorOfSafePtr(Callee))
216271
return true;
217272

218273
auto name = safeGetName(Callee);
@@ -304,6 +359,22 @@ class RawPtrRefCallArgsChecker
304359
Report->setDeclWithIssue(DeclWithIssue);
305360
BR->emitReport(std::move(Report));
306361
}
362+
363+
void reportBugOnReceiver(const Expr *CallArg, const Decl *DeclWithIssue) const {
364+
assert(CallArg);
365+
366+
const SourceLocation SrcLocToReport = CallArg->getSourceRange().getBegin();
367+
368+
SmallString<100> Buf;
369+
llvm::raw_svector_ostream Os(Buf);
370+
Os << "Reciever is " << ptrKind() << " and unsafe.";
371+
372+
PathDiagnosticLocation BSLoc(SrcLocToReport, BR->getSourceManager());
373+
auto Report = std::make_unique<BasicBugReport>(Bug, Os.str(), BSLoc);
374+
Report->addRange(CallArg->getSourceRange());
375+
Report->setDeclWithIssue(DeclWithIssue);
376+
BR->emitReport(std::move(Report));
377+
}
307378
};
308379

309380
class UncountedCallArgsChecker final : public RawPtrRefCallArgsChecker {
@@ -317,7 +388,7 @@ class UncountedCallArgsChecker final : public RawPtrRefCallArgsChecker {
317388
}
318389

319390
std::optional<bool> isUnsafePtr(QualType QT) const final {
320-
return isUncountedPtr(QT);
391+
return isUncountedPtr(QT.getCanonicalType());
321392
}
322393

323394
bool isSafePtr(const CXXRecordDecl *Record) const final {
@@ -342,7 +413,7 @@ class UncheckedCallArgsChecker final : public RawPtrRefCallArgsChecker {
342413
}
343414

344415
std::optional<bool> isUnsafePtr(QualType QT) const final {
345-
return isUncheckedPtr(QT);
416+
return isUncheckedPtr(QT.getCanonicalType());
346417
}
347418

348419
bool isSafePtr(const CXXRecordDecl *Record) const final {
@@ -356,6 +427,33 @@ class UncheckedCallArgsChecker final : public RawPtrRefCallArgsChecker {
356427
const char *ptrKind() const final { return "unchecked"; }
357428
};
358429

430+
class UnretainedCallArgsChecker final : public RawPtrRefCallArgsChecker {
431+
public:
432+
UnretainedCallArgsChecker()
433+
: RawPtrRefCallArgsChecker("Unretained call argument for a raw "
434+
"pointer/reference parameter") {
435+
RTC = RetainTypeChecker();
436+
}
437+
438+
std::optional<bool> isUnsafeType(QualType QT) const final {
439+
return RTC->isUnretained(QT);
440+
}
441+
442+
std::optional<bool> isUnsafePtr(QualType QT) const final {
443+
return RTC->isUnretained(QT);
444+
}
445+
446+
bool isSafePtr(const CXXRecordDecl *Record) const final {
447+
return isRetainPtr(Record);
448+
}
449+
450+
bool isSafePtrType(const QualType type) const final {
451+
return isRetainPtrType(type);
452+
}
453+
454+
const char *ptrKind() const final { return "unretained"; }
455+
};
456+
359457
} // namespace
360458

361459
void ento::registerUncountedCallArgsChecker(CheckerManager &Mgr) {
@@ -373,3 +471,11 @@ void ento::registerUncheckedCallArgsChecker(CheckerManager &Mgr) {
373471
bool ento::shouldRegisterUncheckedCallArgsChecker(const CheckerManager &) {
374472
return true;
375473
}
474+
475+
void ento::registerUnretainedCallArgsChecker(CheckerManager &Mgr) {
476+
Mgr.registerChecker<UnretainedCallArgsChecker>();
477+
}
478+
479+
bool ento::shouldRegisterUnretainedCallArgsChecker(const CheckerManager &) {
480+
return true;
481+
}

0 commit comments

Comments
 (0)