Skip to content

Commit ba3eec3

Browse files
committed
Add unretained call args checker (llvm#130901)
Reland llvm#130729
1 parent 42b9e5c commit ba3eec3

File tree

7 files changed

+608
-8
lines changed

7 files changed

+608
-8
lines changed

Diff for: clang/docs/analyzer/checkers.rst

+6
Original file line numberDiff line numberDiff line change
@@ -3599,6 +3599,12 @@ The goal of this rule is to make sure that lifetime of any dynamically allocated
35993599
36003600
The rules of when to use and not to use CheckedPtr / CheckedRef are same as alpha.webkit.UncountedCallArgsChecker for ref-counted objects.
36013601
3602+
alpha.webkit.UnretainedCallArgsChecker
3603+
""""""""""""""""""""""""""""""""""""""
3604+
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.
3605+
3606+
The rules of when to use and not to use RetainPtr are same as alpha.webkit.UncountedCallArgsChecker for ref-counted objects.
3607+
36023608
alpha.webkit.UncountedLocalVarsChecker
36033609
""""""""""""""""""""""""""""""""""""""
36043610
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 for: clang/include/clang/StaticAnalyzer/Checkers/Checkers.td

+4
Original file line numberDiff line numberDiff line change
@@ -1811,6 +1811,10 @@ def UncheckedCallArgsChecker : Checker<"UncheckedCallArgsChecker">,
18111811
HelpText<"Check unchecked call arguments.">,
18121812
Documentation<HasDocumentation>;
18131813

1814+
def UnretainedCallArgsChecker : Checker<"UnretainedCallArgsChecker">,
1815+
HelpText<"Check unretained call arguments.">,
1816+
Documentation<HasDocumentation>;
1817+
18141818
def UncountedLocalVarsChecker : Checker<"UncountedLocalVarsChecker">,
18151819
HelpText<"Check uncounted local variables.">,
18161820
Documentation<HasDocumentation>;

Diff for: 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()))

Diff for: 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;

Diff for: 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/RecursiveASTVisitor.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") {}
@@ -83,9 +87,22 @@ class RawPtrRefCallArgsChecker
8387
Checker->visitCallExpr(CE, DeclWithIssue);
8488
return true;
8589
}
90+
91+
bool VisitTypedefDecl(TypedefDecl *TD) {
92+
if (Checker->RTC)
93+
Checker->RTC->visitTypedef(TD);
94+
return true;
95+
}
96+
97+
bool VisitObjCMessageExpr(ObjCMessageExpr *ObjCMsgExpr) {
98+
Checker->visitObjCMessageExpr(ObjCMsgExpr, DeclWithIssue);
99+
return true;
100+
}
86101
};
87102

88103
LocalVisitor visitor(this);
104+
if (RTC)
105+
RTC->visitTranslationUnitDecl(TUD);
89106
visitor.TraverseDecl(const_cast<TranslationUnitDecl *>(TUD));
90107
}
91108

@@ -125,7 +142,7 @@ class RawPtrRefCallArgsChecker
125142
// if ((*P)->hasAttr<SafeRefCntblRawPtrAttr>())
126143
// continue;
127144

128-
QualType ArgType = (*P)->getType().getCanonicalType();
145+
QualType ArgType = (*P)->getType();
129146
// FIXME: more complex types (arrays, references to raw pointers, etc)
130147
std::optional<bool> IsUncounted = isUnsafePtr(ArgType);
131148
if (!IsUncounted || !(*IsUncounted))
@@ -141,6 +158,58 @@ class RawPtrRefCallArgsChecker
141158

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

@@ -161,6 +230,8 @@ class RawPtrRefCallArgsChecker
161230
// foo(NULL)
162231
return true;
163232
}
233+
if (isa<ObjCStringLiteral>(ArgOrigin))
234+
return true;
164235
if (isASafeCallArg(ArgOrigin))
165236
return true;
166237
if (EFA.isACallToEnsureFn(ArgOrigin))
@@ -215,7 +286,7 @@ class RawPtrRefCallArgsChecker
215286
overloadedOperatorType == OO_PipePipe)
216287
return true;
217288

218-
if (isCtorOfRefCounted(Callee))
289+
if (isCtorOfSafePtr(Callee))
219290
return true;
220291

221292
auto name = safeGetName(Callee);
@@ -280,9 +351,10 @@ class RawPtrRefCallArgsChecker
280351
}
281352
Os << " is " << ptrKind() << " and unsafe.";
282353

354+
bool usesDefaultArgValue = isa<CXXDefaultArgExpr>(CallArg) && Param;
283355
const SourceLocation SrcLocToReport =
284-
isa<CXXDefaultArgExpr>(CallArg) ? Param->getDefaultArg()->getExprLoc()
285-
: CallArg->getSourceRange().getBegin();
356+
usesDefaultArgValue ? Param->getDefaultArg()->getExprLoc()
357+
: CallArg->getSourceRange().getBegin();
286358

287359
PathDiagnosticLocation BSLoc(SrcLocToReport, BR->getSourceManager());
288360
auto Report = std::make_unique<BasicBugReport>(Bug, Os.str(), BSLoc);
@@ -307,6 +379,23 @@ class RawPtrRefCallArgsChecker
307379
Report->setDeclWithIssue(DeclWithIssue);
308380
BR->emitReport(std::move(Report));
309381
}
382+
383+
void reportBugOnReceiver(const Expr *CallArg,
384+
const Decl *DeclWithIssue) const {
385+
assert(CallArg);
386+
387+
const SourceLocation SrcLocToReport = CallArg->getSourceRange().getBegin();
388+
389+
SmallString<100> Buf;
390+
llvm::raw_svector_ostream Os(Buf);
391+
Os << "Reciever is " << ptrKind() << " and unsafe.";
392+
393+
PathDiagnosticLocation BSLoc(SrcLocToReport, BR->getSourceManager());
394+
auto Report = std::make_unique<BasicBugReport>(Bug, Os.str(), BSLoc);
395+
Report->addRange(CallArg->getSourceRange());
396+
Report->setDeclWithIssue(DeclWithIssue);
397+
BR->emitReport(std::move(Report));
398+
}
310399
};
311400

312401
class UncountedCallArgsChecker final : public RawPtrRefCallArgsChecker {
@@ -320,7 +409,7 @@ class UncountedCallArgsChecker final : public RawPtrRefCallArgsChecker {
320409
}
321410

322411
std::optional<bool> isUnsafePtr(QualType QT) const final {
323-
return isUncountedPtr(QT);
412+
return isUncountedPtr(QT.getCanonicalType());
324413
}
325414

326415
bool isSafePtr(const CXXRecordDecl *Record) const final {
@@ -345,7 +434,7 @@ class UncheckedCallArgsChecker final : public RawPtrRefCallArgsChecker {
345434
}
346435

347436
std::optional<bool> isUnsafePtr(QualType QT) const final {
348-
return isUncheckedPtr(QT);
437+
return isUncheckedPtr(QT.getCanonicalType());
349438
}
350439

351440
bool isSafePtr(const CXXRecordDecl *Record) const final {
@@ -359,6 +448,33 @@ class UncheckedCallArgsChecker final : public RawPtrRefCallArgsChecker {
359448
const char *ptrKind() const final { return "unchecked"; }
360449
};
361450

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

364480
void ento::registerUncountedCallArgsChecker(CheckerManager &Mgr) {
@@ -376,3 +492,11 @@ void ento::registerUncheckedCallArgsChecker(CheckerManager &Mgr) {
376492
bool ento::shouldRegisterUncheckedCallArgsChecker(const CheckerManager &) {
377493
return true;
378494
}
495+
496+
void ento::registerUnretainedCallArgsChecker(CheckerManager &Mgr) {
497+
Mgr.registerChecker<UnretainedCallArgsChecker>();
498+
}
499+
500+
bool ento::shouldRegisterUnretainedCallArgsChecker(const CheckerManager &) {
501+
return true;
502+
}
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)