Skip to content

Commit caf3018

Browse files
authored
Add unretained call args checker (#130901)
Reland #130729
1 parent 378739f commit caf3018

File tree

7 files changed

+608
-8
lines changed

7 files changed

+608
-8
lines changed

clang/docs/analyzer/checkers.rst

+6
Original file line numberDiff line numberDiff line change
@@ -3642,6 +3642,12 @@ The goal of this rule is to make sure that lifetime of any dynamically allocated
36423642
36433643
The rules of when to use and not to use CheckedPtr / CheckedRef are same as alpha.webkit.UncountedCallArgsChecker for ref-counted objects.
36443644
3645+
alpha.webkit.UnretainedCallArgsChecker
3646+
""""""""""""""""""""""""""""""""""""""
3647+
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.
3648+
3649+
The rules of when to use and not to use RetainPtr are same as alpha.webkit.UncountedCallArgsChecker for ref-counted objects.
3650+
36453651
alpha.webkit.UncountedLocalVarsChecker
36463652
""""""""""""""""""""""""""""""""""""""
36473653
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

+4
Original file line numberDiff line numberDiff line change
@@ -1785,6 +1785,10 @@ def UncheckedCallArgsChecker : Checker<"UncheckedCallArgsChecker">,
17851785
HelpText<"Check unchecked call arguments.">,
17861786
Documentation<HasDocumentation>;
17871787

1788+
def UnretainedCallArgsChecker : Checker<"UnretainedCallArgsChecker">,
1789+
HelpText<"Check unretained call arguments.">,
1790+
Documentation<HasDocumentation>;
1791+
17881792
def UncountedLocalVarsChecker : Checker<"UncountedLocalVarsChecker">,
17891793
HelpText<"Check uncounted local variables.">,
17901794
Documentation<HasDocumentation>;

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

+38
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()) { // Treat constants such as kCF* as safe.
38+
return callback(E, true);
39+
}
40+
}
3141
if (auto *tempExpr = dyn_cast<MaterializeTemporaryExpr>(E)) {
3242
E = tempExpr->getSubExpr();
3343
continue;
@@ -57,6 +67,10 @@ bool tryToFindPtrOrigin(
5767
E = tempExpr->getSubExpr();
5868
continue;
5969
}
70+
if (auto *OpaqueValue = dyn_cast<OpaqueValueExpr>(E)) {
71+
E = OpaqueValue->getSourceExpr();
72+
continue;
73+
}
6074
if (auto *Expr = dyn_cast<ConditionalOperator>(E)) {
6175
return tryToFindPtrOrigin(Expr->getTrueExpr(), StopAtFirstRefCountedObj,
6276
isSafePtr, isSafePtrType, callback) &&
@@ -129,14 +143,30 @@ bool tryToFindPtrOrigin(
129143
E = call->getArg(0);
130144
continue;
131145
}
146+
147+
auto Name = safeGetName(callee);
148+
if (Name == "__builtin___CFStringMakeConstantString" ||
149+
Name == "NSClassFromString")
150+
return callback(E, true);
132151
}
133152
}
134153
if (auto *ObjCMsgExpr = dyn_cast<ObjCMessageExpr>(E)) {
135154
if (auto *Method = ObjCMsgExpr->getMethodDecl()) {
136155
if (isSafePtrType(Method->getReturnType()))
137156
return callback(E, true);
138157
}
158+
auto Selector = ObjCMsgExpr->getSelector();
159+
auto NameForFirstSlot = Selector.getNameForSlot(0);
160+
if ((NameForFirstSlot == "class" || NameForFirstSlot == "superclass") &&
161+
!Selector.getNumArgs())
162+
return callback(E, true);
139163
}
164+
if (auto *ObjCDict = dyn_cast<ObjCDictionaryLiteral>(E))
165+
return callback(ObjCDict, true);
166+
if (auto *ObjCArray = dyn_cast<ObjCArrayLiteral>(E))
167+
return callback(ObjCArray, true);
168+
if (auto *ObjCStr = dyn_cast<ObjCStringLiteral>(E))
169+
return callback(ObjCStr, true);
140170
if (auto *unaryOp = dyn_cast<UnaryOperator>(E)) {
141171
// FIXME: Currently accepts ANY unary operator. Is it OK?
142172
E = unaryOp->getSubExpr();
@@ -156,6 +186,14 @@ bool isASafeCallArg(const Expr *E) {
156186
if (auto *D = dyn_cast_or_null<VarDecl>(FoundDecl)) {
157187
if (isa<ParmVarDecl>(D) || D->isLocalVarDecl())
158188
return true;
189+
if (auto *ImplicitP = dyn_cast<ImplicitParamDecl>(D)) {
190+
auto Kind = ImplicitP->getParameterKind();
191+
if (Kind == ImplicitParamKind::ObjCSelf ||
192+
Kind == ImplicitParamKind::ObjCCmd ||
193+
Kind == ImplicitParamKind::CXXThis ||
194+
Kind == ImplicitParamKind::CXXVTT)
195+
return true;
196+
}
159197
} else if (auto *BD = dyn_cast_or_null<BindingDecl>(FoundDecl)) {
160198
VarDecl *VD = BD->getHoldingVar();
161199
if (VD && (isa<ParmVarDecl>(VD) || VD->isLocalVarDecl()))

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

+4-2
Original file line numberDiff line numberDiff line change
@@ -372,7 +372,8 @@ std::optional<bool> isGetterOfSafePtr(const CXXMethodDecl *M) {
372372
if (auto *maybeRefToRawOperator = dyn_cast<CXXConversionDecl>(M)) {
373373
auto QT = maybeRefToRawOperator->getConversionType();
374374
auto *T = QT.getTypePtrOrNull();
375-
return T && (T->isPointerType() || T->isReferenceType());
375+
return T && (T->isPointerType() || T->isReferenceType() ||
376+
T->isObjCObjectPointerType());
376377
}
377378
}
378379
}
@@ -415,7 +416,8 @@ bool isPtrConversion(const FunctionDecl *F) {
415416
if (FunctionName == "getPtr" || FunctionName == "WeakPtr" ||
416417
FunctionName == "dynamicDowncast" || FunctionName == "downcast" ||
417418
FunctionName == "checkedDowncast" ||
418-
FunctionName == "uncheckedDowncast" || FunctionName == "bitwise_cast")
419+
FunctionName == "uncheckedDowncast" || FunctionName == "bitwise_cast" ||
420+
FunctionName == "bridge_cast")
419421
return true;
420422

421423
return false;

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

+130-6
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))
@@ -138,6 +155,58 @@ class RawPtrRefCallArgsChecker
138155

139156
reportBug(Arg, *P, D);
140157
}
158+
for (; ArgIdx < CE->getNumArgs(); ++ArgIdx) {
159+
const auto *Arg = CE->getArg(ArgIdx);
160+
auto ArgType = Arg->getType();
161+
std::optional<bool> IsUncounted = isUnsafePtr(ArgType);
162+
if (!IsUncounted || !(*IsUncounted))
163+
continue;
164+
165+
if (auto *defaultArg = dyn_cast<CXXDefaultArgExpr>(Arg))
166+
Arg = defaultArg->getExpr();
167+
168+
if (isPtrOriginSafe(Arg))
169+
continue;
170+
171+
reportBug(Arg, nullptr, D);
172+
}
173+
}
174+
}
175+
176+
void visitObjCMessageExpr(const ObjCMessageExpr *E, const Decl *D) const {
177+
if (BR->getSourceManager().isInSystemHeader(E->getExprLoc()))
178+
return;
179+
180+
auto Selector = E->getSelector();
181+
if (auto *Receiver = E->getInstanceReceiver()) {
182+
std::optional<bool> IsUnsafe = isUnsafePtr(E->getReceiverType());
183+
if (IsUnsafe && *IsUnsafe && !isPtrOriginSafe(Receiver)) {
184+
if (auto *InnerMsg = dyn_cast<ObjCMessageExpr>(Receiver)) {
185+
auto InnerSelector = InnerMsg->getSelector();
186+
if (InnerSelector.getNameForSlot(0) == "alloc" &&
187+
Selector.getNameForSlot(0).starts_with("init"))
188+
return;
189+
}
190+
reportBugOnReceiver(Receiver, D);
191+
}
192+
}
193+
194+
auto *MethodDecl = E->getMethodDecl();
195+
if (!MethodDecl)
196+
return;
197+
198+
auto ArgCount = E->getNumArgs();
199+
for (unsigned i = 0; i < ArgCount; ++i) {
200+
auto *Arg = E->getArg(i);
201+
bool hasParam = i < MethodDecl->param_size();
202+
auto *Param = hasParam ? MethodDecl->getParamDecl(i) : nullptr;
203+
auto ArgType = Arg->getType();
204+
std::optional<bool> IsUnsafe = isUnsafePtr(ArgType);
205+
if (!IsUnsafe || !(*IsUnsafe))
206+
continue;
207+
if (isPtrOriginSafe(Arg))
208+
continue;
209+
reportBug(Arg, Param, D);
141210
}
142211
}
143212

@@ -158,6 +227,8 @@ class RawPtrRefCallArgsChecker
158227
// foo(NULL)
159228
return true;
160229
}
230+
if (isa<ObjCStringLiteral>(ArgOrigin))
231+
return true;
161232
if (isASafeCallArg(ArgOrigin))
162233
return true;
163234
if (EFA.isACallToEnsureFn(ArgOrigin))
@@ -212,7 +283,7 @@ class RawPtrRefCallArgsChecker
212283
overloadedOperatorType == OO_PipePipe)
213284
return true;
214285

215-
if (isCtorOfRefCounted(Callee))
286+
if (isCtorOfSafePtr(Callee))
216287
return true;
217288

218289
auto name = safeGetName(Callee);
@@ -277,9 +348,10 @@ class RawPtrRefCallArgsChecker
277348
}
278349
Os << " is " << ptrKind() << " and unsafe.";
279350

351+
bool usesDefaultArgValue = isa<CXXDefaultArgExpr>(CallArg) && Param;
280352
const SourceLocation SrcLocToReport =
281-
isa<CXXDefaultArgExpr>(CallArg) ? Param->getDefaultArg()->getExprLoc()
282-
: CallArg->getSourceRange().getBegin();
353+
usesDefaultArgValue ? Param->getDefaultArg()->getExprLoc()
354+
: CallArg->getSourceRange().getBegin();
283355

284356
PathDiagnosticLocation BSLoc(SrcLocToReport, BR->getSourceManager());
285357
auto Report = std::make_unique<BasicBugReport>(Bug, Os.str(), BSLoc);
@@ -304,6 +376,23 @@ class RawPtrRefCallArgsChecker
304376
Report->setDeclWithIssue(DeclWithIssue);
305377
BR->emitReport(std::move(Report));
306378
}
379+
380+
void reportBugOnReceiver(const Expr *CallArg,
381+
const Decl *DeclWithIssue) const {
382+
assert(CallArg);
383+
384+
const SourceLocation SrcLocToReport = CallArg->getSourceRange().getBegin();
385+
386+
SmallString<100> Buf;
387+
llvm::raw_svector_ostream Os(Buf);
388+
Os << "Reciever is " << ptrKind() << " and unsafe.";
389+
390+
PathDiagnosticLocation BSLoc(SrcLocToReport, BR->getSourceManager());
391+
auto Report = std::make_unique<BasicBugReport>(Bug, Os.str(), BSLoc);
392+
Report->addRange(CallArg->getSourceRange());
393+
Report->setDeclWithIssue(DeclWithIssue);
394+
BR->emitReport(std::move(Report));
395+
}
307396
};
308397

309398
class UncountedCallArgsChecker final : public RawPtrRefCallArgsChecker {
@@ -317,7 +406,7 @@ class UncountedCallArgsChecker final : public RawPtrRefCallArgsChecker {
317406
}
318407

319408
std::optional<bool> isUnsafePtr(QualType QT) const final {
320-
return isUncountedPtr(QT);
409+
return isUncountedPtr(QT.getCanonicalType());
321410
}
322411

323412
bool isSafePtr(const CXXRecordDecl *Record) const final {
@@ -342,7 +431,7 @@ class UncheckedCallArgsChecker final : public RawPtrRefCallArgsChecker {
342431
}
343432

344433
std::optional<bool> isUnsafePtr(QualType QT) const final {
345-
return isUncheckedPtr(QT);
434+
return isUncheckedPtr(QT.getCanonicalType());
346435
}
347436

348437
bool isSafePtr(const CXXRecordDecl *Record) const final {
@@ -356,6 +445,33 @@ class UncheckedCallArgsChecker final : public RawPtrRefCallArgsChecker {
356445
const char *ptrKind() const final { return "unchecked"; }
357446
};
358447

448+
class UnretainedCallArgsChecker final : public RawPtrRefCallArgsChecker {
449+
public:
450+
UnretainedCallArgsChecker()
451+
: RawPtrRefCallArgsChecker("Unretained call argument for a raw "
452+
"pointer/reference parameter") {
453+
RTC = RetainTypeChecker();
454+
}
455+
456+
std::optional<bool> isUnsafeType(QualType QT) const final {
457+
return RTC->isUnretained(QT);
458+
}
459+
460+
std::optional<bool> isUnsafePtr(QualType QT) const final {
461+
return RTC->isUnretained(QT);
462+
}
463+
464+
bool isSafePtr(const CXXRecordDecl *Record) const final {
465+
return isRetainPtr(Record);
466+
}
467+
468+
bool isSafePtrType(const QualType type) const final {
469+
return isRetainPtrType(type);
470+
}
471+
472+
const char *ptrKind() const final { return "unretained"; }
473+
};
474+
359475
} // namespace
360476

361477
void ento::registerUncountedCallArgsChecker(CheckerManager &Mgr) {
@@ -373,3 +489,11 @@ void ento::registerUncheckedCallArgsChecker(CheckerManager &Mgr) {
373489
bool ento::shouldRegisterUncheckedCallArgsChecker(const CheckerManager &) {
374490
return true;
375491
}
492+
493+
void ento::registerUnretainedCallArgsChecker(CheckerManager &Mgr) {
494+
Mgr.registerChecker<UnretainedCallArgsChecker>();
495+
}
496+
497+
bool ento::shouldRegisterUnretainedCallArgsChecker(const CheckerManager &) {
498+
return true;
499+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
// RUN: %clang_analyze_cc1 -analyzer-checker=alpha.webkit.UnretainedCallArgsChecker -fobjc-arc -verify %s
2+
3+
#import "objc-mock-types.h"
4+
5+
SomeObj *provide();
6+
CFMutableArrayRef provide_cf();
7+
void someFunction();
8+
9+
namespace raw_ptr {
10+
11+
void foo() {
12+
[provide() doWork];
13+
CFArrayAppendValue(provide_cf(), nullptr);
14+
// expected-warning@-1{{Call argument for parameter 'theArray' is unretained and unsafe [alpha.webkit.UnretainedCallArgsChecker]}}
15+
}
16+
17+
} // namespace raw_ptr
18+
19+
@interface AnotherObj : NSObject
20+
- (void)foo:(SomeObj *)obj;
21+
@end
22+
23+
@implementation AnotherObj
24+
- (void)foo:(SomeObj*)obj {
25+
[obj doWork];
26+
[provide() doWork];
27+
CFArrayAppendValue(provide_cf(), nullptr);
28+
// expected-warning@-1{{Call argument for parameter 'theArray' is unretained and unsafe [alpha.webkit.UnretainedCallArgsChecker]}}
29+
}
30+
@end

0 commit comments

Comments
 (0)